EOPHistory.java

  1. /* Copyright 2002-2022 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.frames;

  18. 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.CalculusFieldElement;
  27. import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
  28. import org.hipparchus.analysis.interpolation.HermiteInterpolator;
  29. import org.hipparchus.util.FastMath;
  30. import org.hipparchus.util.MathArrays;
  31. import org.orekit.annotation.DefaultDataContext;
  32. import org.orekit.data.DataContext;
  33. import org.orekit.errors.OrekitException;
  34. import org.orekit.errors.OrekitInternalError;
  35. import org.orekit.errors.OrekitMessages;
  36. import org.orekit.errors.TimeStampedCacheException;
  37. import org.orekit.time.AbsoluteDate;
  38. import org.orekit.time.FieldAbsoluteDate;
  39. import org.orekit.time.TimeScales;
  40. import org.orekit.time.TimeStamped;
  41. import org.orekit.time.TimeVectorFunction;
  42. import org.orekit.utils.Constants;
  43. import org.orekit.utils.GenericTimeStampedCache;
  44. import org.orekit.utils.IERSConventions;
  45. import org.orekit.utils.ImmutableTimeStampedCache;
  46. import org.orekit.utils.OrekitConfiguration;
  47. import org.orekit.utils.TimeStampedCache;
  48. import org.orekit.utils.TimeStampedGenerator;

  49. /** This class loads any kind of Earth Orientation Parameter data throughout a large time range.
  50.  * @author Pascal Parraud
  51.  * @author Evan Ward
  52.  */
  53. public class EOPHistory implements Serializable {

  54.     /** Serializable UID. */
  55.     private static final long serialVersionUID = 20191119L;

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

  58.     /**
  59.      * If this history has any EOP data.
  60.      *
  61.      * @see #hasDataFor(AbsoluteDate)
  62.      */
  63.     private final boolean hasData;

  64.     /** EOP history entries. */
  65.     private final transient ImmutableTimeStampedCache<EOPEntry> cache;

  66.     /** IERS conventions to which EOP refers. */
  67.     private final IERSConventions conventions;

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

  70.     /** Time scales to use when computing corrections. */
  71.     private final transient TimeScales timeScales;

  72.     /** Simple constructor.
  73.      *
  74.      * <p>This method uses the {@link DataContext#getDefault() default data context}.
  75.      *
  76.      * @param conventions IERS conventions to which EOP refers
  77.      * @param data the EOP data to use
  78.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  79.      * @see #EOPHistory(IERSConventions, Collection, boolean, TimeScales)
  80.      */
  81.     @DefaultDataContext
  82.     protected EOPHistory(final IERSConventions conventions,
  83.                          final Collection<? extends EOPEntry> data,
  84.                          final boolean simpleEOP) {
  85.         this(conventions, data, simpleEOP, DataContext.getDefault().getTimeScales());
  86.     }

  87.     /** Simple constructor.
  88.      * @param conventions IERS conventions to which EOP refers
  89.      * @param data the EOP data to use
  90.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  91.      * @param timeScales to use when computing EOP corrections.
  92.      * @since 10.1
  93.      */
  94.     public EOPHistory(final IERSConventions conventions,
  95.                       final Collection<? extends EOPEntry> data,
  96.                       final boolean simpleEOP,
  97.                       final TimeScales timeScales) {
  98.         this(conventions,
  99.                 data,
  100.                 simpleEOP ? null : new CachedCorrection(conventions.getEOPTidalCorrection(timeScales)),
  101.                 timeScales);
  102.     }

  103.     /** Simple constructor.
  104.      * @param conventions IERS conventions to which EOP refers
  105.      * @param data the EOP data to use
  106.      * @param tidalCorrection correction to apply to EOP
  107.      * @param timeScales to use when computing EOP corrections.
  108.      * @since 10.1
  109.      */
  110.     private EOPHistory(final IERSConventions conventions,
  111.                        final Collection<? extends EOPEntry> data,
  112.                        final TimeVectorFunction tidalCorrection,
  113.                        final TimeScales timeScales) {
  114.         this.conventions      = conventions;
  115.         this.tidalCorrection  = tidalCorrection;
  116.         this.timeScales = timeScales;
  117.         if (data.size() >= 1) {
  118.             // enough data to interpolate
  119.             cache = new ImmutableTimeStampedCache<EOPEntry>(FastMath.min(INTERPOLATION_POINTS, data.size()), data);
  120.             hasData = true;
  121.         } else {
  122.             // not enough data to interpolate -> always use null correction
  123.             cache = ImmutableTimeStampedCache.emptyCache();
  124.             hasData = false;
  125.         }
  126.     }

  127.     /**
  128.      * Determine if this history uses simplified EOP corrections.
  129.      *
  130.      * @return {@code true} if tidal corrections are ignored, {@code false} otherwise.
  131.      */
  132.     public boolean isSimpleEop() {
  133.         return tidalCorrection == null;
  134.     }

  135.     /**
  136.      * Get the time scales used in computing EOP corrections.
  137.      *
  138.      * @return set of time scales.
  139.      * @since 10.1
  140.      */
  141.     public TimeScales getTimeScales() {
  142.         return timeScales;
  143.     }

  144.     /** Get non-interpolating version of the instance.
  145.      * @return non-interpolatig version of the instance
  146.      */
  147.     public EOPHistory getNonInterpolatingEOPHistory() {
  148.         return new EOPHistory(conventions, getEntries(),
  149.                 conventions.getEOPTidalCorrection(timeScales), timeScales);
  150.     }

  151.     /** Check if the instance uses interpolation on tidal corrections.
  152.      * @return true if the instance uses interpolation on tidal corrections
  153.      */
  154.     public boolean usesInterpolation() {
  155.         return tidalCorrection instanceof CachedCorrection;
  156.     }

  157.     /** Get the IERS conventions to which these EOP apply.
  158.      * @return IERS conventions to which these EOP apply
  159.      */
  160.     public IERSConventions getConventions() {
  161.         return conventions;
  162.     }

  163.     /** Get the date of the first available Earth Orientation Parameters.
  164.      * @return the start date of the available data
  165.      */
  166.     public AbsoluteDate getStartDate() {
  167.         return this.cache.getEarliest().getDate();
  168.     }

  169.     /** Get the date of the last available Earth Orientation Parameters.
  170.      * @return the end date of the available data
  171.      */
  172.     public AbsoluteDate getEndDate() {
  173.         return this.cache.getLatest().getDate();
  174.     }

  175.     /** Get the UT1-UTC value.
  176.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  177.      * @param date date at which the value is desired
  178.      * @return UT1-UTC in seconds (0 if date is outside covered range)
  179.      */
  180.     public double getUT1MinusUTC(final AbsoluteDate date) {

  181.         //check if there is data for date
  182.         if (!this.hasDataFor(date)) {
  183.             // no EOP data available for this date, we use a default 0.0 offset
  184.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[2];
  185.         }

  186.         // we have EOP data -> interpolate offset
  187.         try {
  188.             final DUT1Interpolator interpolator = new DUT1Interpolator(date);
  189.             getNeighbors(date).forEach(interpolator);
  190.             double interpolated = interpolator.getInterpolated();
  191.             if (tidalCorrection != null) {
  192.                 interpolated += tidalCorrection.value(date)[2];
  193.             }
  194.             return interpolated;
  195.         } catch (TimeStampedCacheException tce) {
  196.             //this should not happen because of date check above
  197.             throw new OrekitInternalError(tce);
  198.         }

  199.     }

  200.     /** Get the UT1-UTC value.
  201.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  202.      * @param date date at which the value is desired
  203.      * @param <T> type of the field elements
  204.      * @return UT1-UTC in seconds (0 if date is outside covered range)
  205.      * @since 9.0
  206.      */
  207.     public <T extends CalculusFieldElement<T>> T getUT1MinusUTC(final FieldAbsoluteDate<T> date) {

  208.         //check if there is data for date
  209.         final AbsoluteDate absDate = date.toAbsoluteDate();
  210.         if (!this.hasDataFor(absDate)) {
  211.             // no EOP data available for this date, we use a default 0.0 offset
  212.             return (tidalCorrection == null) ? date.getField().getZero() : tidalCorrection.value(date)[2];
  213.         }

  214.         // we have EOP data -> interpolate offset
  215.         try {
  216.             final FieldDUT1Interpolator<T> interpolator = new FieldDUT1Interpolator<>(date, absDate);
  217.             getNeighbors(absDate).forEach(interpolator);
  218.             T interpolated = interpolator.getInterpolated();
  219.             if (tidalCorrection != null) {
  220.                 interpolated = interpolated.add(tidalCorrection.value(date)[2]);
  221.             }
  222.             return interpolated;
  223.         } catch (TimeStampedCacheException tce) {
  224.             //this should not happen because of date check above
  225.             throw new OrekitInternalError(tce);
  226.         }

  227.     }

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

  230.         /** DUT at first entry. */
  231.         private double firstDUT;

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

  234.         /** Interpolator to use. */
  235.         private final HermiteInterpolator interpolator;

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

  238.         /** Simple constructor.
  239.          * @param date interpolation date
  240.          */
  241.         DUT1Interpolator(final AbsoluteDate date) {
  242.             this.firstDUT     = Double.NaN;
  243.             this.beforeLeap   = true;
  244.             this.interpolator = new HermiteInterpolator();
  245.             this.date         = date;
  246.         }

  247.         /** {@inheritDoc} */
  248.         @Override
  249.         public void accept(final EOPEntry neighbor) {
  250.             if (Double.isNaN(firstDUT)) {
  251.                 firstDUT = neighbor.getUT1MinusUTC();
  252.             }
  253.             final double dut;
  254.             if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
  255.                 // there was a leap second between the entries
  256.                 dut = neighbor.getUT1MinusUTC() - 1.0;
  257.                 // UTCScale considers the discontinuity to occur at the start of the leap
  258.                 // second so this code must use the same convention. EOP entries are time
  259.                 // stamped at midnight UTC so 1 second before is the start of the leap
  260.                 // second.
  261.                 if (neighbor.getDate().shiftedBy(-1).compareTo(date) <= 0) {
  262.                     beforeLeap = false;
  263.                 }
  264.             } else {
  265.                 dut = neighbor.getUT1MinusUTC();
  266.             }
  267.             interpolator.addSamplePoint(neighbor.getDate().durationFrom(date),
  268.                                         new double[] {
  269.                                             dut
  270.                                         });
  271.         }

  272.         /** Get the interpolated value.
  273.          * @return interpolated value
  274.          */
  275.         public double getInterpolated() {
  276.             final double interpolated = interpolator.value(0)[0];
  277.             return beforeLeap ? interpolated : interpolated + 1.0;
  278.         }

  279.     }

  280.     /** Local class for DUT1 interpolation, crossing leaps safely. */
  281.     private static class FieldDUT1Interpolator<T extends CalculusFieldElement<T>> implements Consumer<EOPEntry> {

  282.         /** DUT at first entry. */
  283.         private double firstDUT;

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

  286.         /** Interpolator to use. */
  287.         private final FieldHermiteInterpolator<T> interpolator;

  288.         /** Interpolation date. */
  289.         private FieldAbsoluteDate<T> date;

  290.         /** Interpolation date. */
  291.         private AbsoluteDate absDate;

  292.         /** Simple constructor.
  293.          * @param date interpolation date
  294.          * @param absDate interpolation date
  295.          */
  296.         FieldDUT1Interpolator(final FieldAbsoluteDate<T> date, final AbsoluteDate absDate) {
  297.             this.firstDUT     = Double.NaN;
  298.             this.beforeLeap   = true;
  299.             this.interpolator = new FieldHermiteInterpolator<>();
  300.             this.date         = date;
  301.             this.absDate      = absDate;
  302.         }

  303.         /** {@inheritDoc} */
  304.         @Override
  305.         public void accept(final EOPEntry neighbor) {
  306.             if (Double.isNaN(firstDUT)) {
  307.                 firstDUT = neighbor.getUT1MinusUTC();
  308.             }
  309.             final double dut;
  310.             if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
  311.                 // there was a leap second between the entries
  312.                 dut = neighbor.getUT1MinusUTC() - 1.0;
  313.                 if (neighbor.getDate().compareTo(absDate) <= 0) {
  314.                     beforeLeap = false;
  315.                 }
  316.             } else {
  317.                 dut = neighbor.getUT1MinusUTC();
  318.             }
  319.             final T[] array = MathArrays.buildArray(date.getField(), 1);
  320.             array[0] = date.getField().getZero().add(dut);
  321.             interpolator.addSamplePoint(date.durationFrom(neighbor.getDate()).negate(),
  322.                                         array);
  323.         }

  324.         /** Get the interpolated value.
  325.          * @return interpolated value
  326.          */
  327.         public T getInterpolated() {
  328.             final T interpolated = interpolator.value(date.getField().getZero())[0];
  329.             return beforeLeap ? interpolated : interpolated.add(1.0);
  330.         }

  331.     }

  332.     /**
  333.      * Get the entries surrounding a central date.
  334.      * <p>
  335.      * See {@link #hasDataFor(AbsoluteDate)} to determine if the cache has data
  336.      * for {@code central} without throwing an exception.
  337.      *
  338.      * @param central central date
  339.      * @return array of cached entries surrounding specified date
  340.      */
  341.     protected Stream<EOPEntry> getNeighbors(final AbsoluteDate central) {
  342.         return cache.getNeighbors(central);
  343.     }

  344.     /** Get the LoD (Length of Day) value.
  345.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  346.      * @param date date at which the value is desired
  347.      * @return LoD in seconds (0 if date is outside covered range)
  348.      */
  349.     public double getLOD(final AbsoluteDate date) {

  350.         // check if there is data for date
  351.         if (!this.hasDataFor(date)) {
  352.             // no EOP data available for this date, we use a default null correction
  353.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[3];
  354.         }

  355.         // we have EOP data for date -> interpolate correction
  356.         double interpolated = interpolate(date, entry -> entry.getLOD());
  357.         if (tidalCorrection != null) {
  358.             interpolated += tidalCorrection.value(date)[3];
  359.         }
  360.         return interpolated;

  361.     }

  362.     /** Get the LoD (Length of Day) value.
  363.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  364.      * @param date date at which the value is desired
  365.      * @param <T> type of the filed elements
  366.      * @return LoD in seconds (0 if date is outside covered range)
  367.      * @since 9.0
  368.      */
  369.     public <T extends CalculusFieldElement<T>> T getLOD(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.             return (tidalCorrection == null) ? date.getField().getZero() : tidalCorrection.value(date)[3];
  375.         }

  376.         // we have EOP data for date -> interpolate correction
  377.         T interpolated = interpolate(date, aDate, entry -> entry.getLOD());
  378.         if (tidalCorrection != null) {
  379.             interpolated = interpolated.add(tidalCorrection.value(date)[3]);
  380.         }

  381.         return interpolated;

  382.     }

  383.     /** Get the pole IERS Reference Pole correction.
  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 pole correction ({@link PoleCorrection#NULL_CORRECTION
  387.      * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
  388.      */
  389.     public PoleCorrection getPoleCorrection(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.             if (tidalCorrection == null) {
  394.                 return PoleCorrection.NULL_CORRECTION;
  395.             } else {
  396.                 final double[] correction = tidalCorrection.value(date);
  397.                 return new PoleCorrection(correction[0], correction[1]);
  398.             }
  399.         }

  400.         // we have EOP data for date -> interpolate correction
  401.         final double[] interpolated = interpolate(date, entry -> entry.getX(), entry -> entry.getY());
  402.         if (tidalCorrection != null) {
  403.             final double[] correction = tidalCorrection.value(date);
  404.             interpolated[0] += correction[0];
  405.             interpolated[1] += correction[1];
  406.         }
  407.         return new PoleCorrection(interpolated[0], interpolated[1]);

  408.     }

  409.     /** Get the pole IERS Reference Pole correction.
  410.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  411.      * @param date date at which the correction is desired
  412.      * @param <T> type of the field elements
  413.      * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
  414.      * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
  415.      */
  416.     public <T extends CalculusFieldElement<T>> FieldPoleCorrection<T> getPoleCorrection(final FieldAbsoluteDate<T> date) {

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

  418.         // check if there is data for date
  419.         if (!this.hasDataFor(aDate)) {
  420.             // no EOP data available for this date, we use a default null correction
  421.             if (tidalCorrection == null) {
  422.                 return new FieldPoleCorrection<>(date.getField().getZero(), date.getField().getZero());
  423.             } else {
  424.                 final T[] correction = tidalCorrection.value(date);
  425.                 return new FieldPoleCorrection<>(correction[0], correction[1]);
  426.             }
  427.         }

  428.         // we have EOP data for date -> interpolate correction
  429.         final T[] interpolated = interpolate(date, aDate, entry -> entry.getX(), entry -> entry.getY());
  430.         if (tidalCorrection != null) {
  431.             final T[] correction = tidalCorrection.value(date);
  432.             interpolated[0] = interpolated[0].add(correction[0]);
  433.             interpolated[1] = interpolated[1].add(correction[1]);
  434.         }
  435.         return new FieldPoleCorrection<>(interpolated[0], interpolated[1]);

  436.     }

  437.     /** Get the correction to the nutation parameters for equinox-based 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.      * @return nutation correction in longitude ΔΨ and in obliquity Δε
  441.      * (zero if date is outside covered range)
  442.      */
  443.     public double[] getEquinoxNutationCorrection(final AbsoluteDate date) {

  444.         // check if there is data for date
  445.         if (!this.hasDataFor(date)) {
  446.             // no EOP data available for this date, we use a default null correction
  447.             return new double[2];
  448.         }

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

  451.     }

  452.     /** Get the correction to the nutation parameters for equinox-based paradigm.
  453.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  454.      * @param date date at which the correction is desired
  455.      * @param <T> type of the field elements
  456.      * @return nutation correction in longitude ΔΨ and in obliquity Δε
  457.      * (zero if date is outside covered range)
  458.      */
  459.     public <T extends CalculusFieldElement<T>> T[] getEquinoxNutationCorrection(final FieldAbsoluteDate<T> date) {

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

  461.         // check if there is data for date
  462.         if (!this.hasDataFor(aDate)) {
  463.             // no EOP data available for this date, we use a default null correction
  464.             return MathArrays.buildArray(date.getField(), 2);
  465.         }

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

  468.     }

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

  476.         // check if there is data for date
  477.         if (!this.hasDataFor(date)) {
  478.             // no EOP data available for this date, we use a default null correction
  479.             return new double[2];
  480.         }

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

  483.     }

  484.     /** Get the correction to the nutation parameters for Non-Rotating Origin paradigm.
  485.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  486.      * @param date date at which the correction is desired
  487.      * @param <T> type of the filed elements
  488.      * @return nutation correction in Celestial Intermediat Pole coordinates
  489.      * δX and δY (zero if date is outside covered range)
  490.      */
  491.     public <T extends CalculusFieldElement<T>> T[] getNonRotatinOriginNutationCorrection(final FieldAbsoluteDate<T> date) {

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

  493.         // check if there is data for date
  494.         if (!this.hasDataFor(aDate)) {
  495.             // no EOP data available for this date, we use a default null correction
  496.             return MathArrays.buildArray(date.getField(), 2);
  497.         }

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

  500.     }

  501.     /** Get the ITRF version.
  502.      * @param date date at which the value is desired
  503.      * @return ITRF version of the EOP covering the specified date
  504.      * @since 9.2
  505.      */
  506.     public ITRFVersion getITRFVersion(final AbsoluteDate date) {

  507.         // check if there is data for date
  508.         if (!this.hasDataFor(date)) {
  509.             // no EOP data available for this date, we use a default ITRF 2014
  510.             return ITRFVersion.ITRF_2014;
  511.         }

  512.         try {
  513.             // we have EOP data for date
  514.             final Optional<EOPEntry> first = getNeighbors(date).findFirst();
  515.             return first.isPresent() ? first.get().getITRFType() : ITRFVersion.ITRF_2014;

  516.         } catch (TimeStampedCacheException tce) {
  517.             // this should not happen because of date check performed at start
  518.             throw new OrekitInternalError(tce);
  519.         }

  520.     }

  521.     /** Check Earth orientation parameters continuity.
  522.      * @param maxGap maximal allowed gap between entries (in seconds)
  523.      */
  524.     public void checkEOPContinuity(final double maxGap) {
  525.         TimeStamped preceding = null;
  526.         for (final TimeStamped current : this.cache.getAll()) {

  527.             // compare the dates of preceding and current entries
  528.             if (preceding != null && (current.getDate().durationFrom(preceding.getDate())) > maxGap) {
  529.                 throw new OrekitException(OrekitMessages.MISSING_EARTH_ORIENTATION_PARAMETERS_BETWEEN_DATES_GAP,
  530.                                           preceding.getDate(), current.getDate(),
  531.                                           current.getDate().durationFrom(preceding.getDate()));
  532.             }

  533.             // prepare next iteration
  534.             preceding = current;

  535.         }
  536.     }

  537.     /**
  538.      * Check if the cache has data for the given date using
  539.      * {@link #getStartDate()} and {@link #getEndDate()}.
  540.      *
  541.      * @param date the requested date
  542.      * @return true if the {@link #cache} has data for the requested date, false
  543.      *         otherwise.
  544.      */
  545.     protected boolean hasDataFor(final AbsoluteDate date) {
  546.         /*
  547.          * when there is no EOP data, short circuit getStartDate, which will
  548.          * throw an exception
  549.          */
  550.         return this.hasData && this.getStartDate().compareTo(date) <= 0 &&
  551.                date.compareTo(this.getEndDate()) <= 0;
  552.     }

  553.     /** Get a non-modifiable view of the EOP entries.
  554.      * @return non-modifiable view of the EOP entries
  555.      */
  556.     public List<EOPEntry> getEntries() {
  557.         return cache.getAll();
  558.     }

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

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

  611.     /** Interpolate two EOP components.
  612.      * <p>
  613.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  614.      * </p>
  615.      * @param date interpolation date
  616.      * @param selector1 selector for first EOP entry component
  617.      * @param selector2 selector for second EOP entry component
  618.      * @return interpolated value
  619.      */
  620.     private double[] interpolate(final AbsoluteDate date,
  621.                                  final Function<EOPEntry, Double> selector1,
  622.                                  final Function<EOPEntry, Double> selector2) {
  623.         try {
  624.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  625.             getNeighbors(date).forEach(entry ->
  626.                                        interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  627.                                                                    new double[] {
  628.                                                                        selector1.apply(entry),
  629.                                                                        selector2.apply(entry)
  630.                                                                    }));
  631.             return interpolator.value(0);
  632.         } catch (TimeStampedCacheException tce) {
  633.             // this should not happen because of date check performed by caller
  634.             throw new OrekitInternalError(tce);
  635.         }
  636.     }

  637.     /** Interpolate two EOP components.
  638.      * <p>
  639.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  640.      * </p>
  641.      * @param date interpolation date
  642.      * @param aDate interpolation date, as an {@link AbsoluteDate}
  643.      * @param selector1 selector for first EOP entry component
  644.      * @param selector2 selector for second EOP entry component
  645.      * @param <T> type of the field elements
  646.      * @return interpolated value
  647.      */
  648.     private <T extends CalculusFieldElement<T>> T[] interpolate(final FieldAbsoluteDate<T> date,
  649.                                                             final AbsoluteDate aDate,
  650.                                                             final Function<EOPEntry, Double> selector1,
  651.                                                             final Function<EOPEntry, Double> selector2) {
  652.         try {
  653.             final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
  654.             final T[] y = MathArrays.buildArray(date.getField(), 2);
  655.             final T zero = date.getField().getZero();
  656.             final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
  657.                                                                                        // for example removing derivatives
  658.                                                                                        // if T was DerivativeStructure
  659.             getNeighbors(aDate).forEach(entry -> {
  660.                 y[0] = zero.add(selector1.apply(entry));
  661.                 y[1] = zero.add(selector2.apply(entry));
  662.                 interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
  663.             });
  664.             return interpolator.value(date.durationFrom(central)); // here, we introduce derivatives again (in DerivativeStructure case)
  665.         } catch (TimeStampedCacheException tce) {
  666.             // this should not happen because of date check performed by caller
  667.             throw new OrekitInternalError(tce);
  668.         }
  669.     }

  670.     /** Replace the instance with a data transfer object for serialization.
  671.      * <p>
  672.      * This intermediate class serializes only the frame key.
  673.      * </p>
  674.      * @return data transfer object that will be serialized
  675.      */
  676.     @DefaultDataContext
  677.     private Object writeReplace() {
  678.         return new DataTransferObject(conventions, getEntries(), tidalCorrection == null);
  679.     }

  680.     /** Internal class used only for serialization. */
  681.     @DefaultDataContext
  682.     private static class DataTransferObject implements Serializable {

  683.         /** Serializable UID. */
  684.         private static final long serialVersionUID = 20131010L;

  685.         /** IERS conventions. */
  686.         private final IERSConventions conventions;

  687.         /** EOP entries. */
  688.         private final List<EOPEntry> entries;

  689.         /** Indicator for simple interpolation without tidal effects. */
  690.         private final boolean simpleEOP;

  691.         /** Simple constructor.
  692.          * @param conventions IERS conventions to which EOP refers
  693.          * @param entries the EOP data to use
  694.          * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  695.          */
  696.         DataTransferObject(final IERSConventions conventions,
  697.                                   final List<EOPEntry> entries,
  698.                                   final boolean simpleEOP) {
  699.             this.conventions = conventions;
  700.             this.entries     = entries;
  701.             this.simpleEOP   = simpleEOP;
  702.         }

  703.         /** Replace the deserialized data transfer object with a {@link EOPHistory}.
  704.          * @return replacement {@link EOPHistory}
  705.          */
  706.         private Object readResolve() {
  707.             try {
  708.                 // retrieve a managed frame
  709.                 return new EOPHistory(conventions, entries, simpleEOP);
  710.             } catch (OrekitException oe) {
  711.                 throw new OrekitInternalError(oe);
  712.             }
  713.         }

  714.     }

  715.     /** Internal class for caching tidal correction. */
  716.     private static class TidalCorrectionEntry implements TimeStamped {

  717.         /** Entry date. */
  718.         private final AbsoluteDate date;

  719.         /** Correction. */
  720.         private final double[] correction;

  721.         /** Simple constructor.
  722.          * @param date entry date
  723.          * @param correction correction on the EOP parameters (xp, yp, ut1, lod)
  724.          */
  725.         TidalCorrectionEntry(final AbsoluteDate date, final double[] correction) {
  726.             this.date       = date;
  727.             this.correction = correction;
  728.         }

  729.         /** {@inheritDoc} */
  730.         @Override
  731.         public AbsoluteDate getDate() {
  732.             return date;
  733.         }

  734.     }

  735.     /** Local generator for thread-safe cache. */
  736.     private static class CachedCorrection
  737.         implements TimeVectorFunction, TimeStampedGenerator<TidalCorrectionEntry> {

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

  740.         /** Step between generated entries. */
  741.         private final double step;

  742.         /** Tidal corrections entries cache. */
  743.         private final TimeStampedCache<TidalCorrectionEntry> cache;

  744.         /** Simple constructor.
  745.          * @param tidalCorrection function computing the tidal correction
  746.          */
  747.         CachedCorrection(final TimeVectorFunction tidalCorrection) {
  748.             this.step            = 60 * 60;
  749.             this.tidalCorrection = tidalCorrection;
  750.             this.cache           =
  751.                 new GenericTimeStampedCache<TidalCorrectionEntry>(8,
  752.                                                                   OrekitConfiguration.getCacheSlotsNumber(),
  753.                                                                   Constants.JULIAN_DAY * 30,
  754.                                                                   Constants.JULIAN_DAY,
  755.                                                                   this);
  756.         }

  757.         /** {@inheritDoc} */
  758.         @Override
  759.         public double[] value(final AbsoluteDate date) {
  760.             try {
  761.                 // set up an interpolator
  762.                 final HermiteInterpolator interpolator = new HermiteInterpolator();
  763.                 cache.getNeighbors(date).forEach(entry -> interpolator.addSamplePoint(entry.date.durationFrom(date), entry.correction));

  764.                 // interpolate to specified date
  765.                 return interpolator.value(0.0);
  766.             } catch (TimeStampedCacheException tsce) {
  767.                 // this should never happen
  768.                 throw new OrekitInternalError(tsce);
  769.             }
  770.         }

  771.         /** {@inheritDoc} */
  772.         @Override
  773.         public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
  774.             try {

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

  776.                 final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
  777.                 final T[] y = MathArrays.buildArray(date.getField(), 4);
  778.                 final T zero = date.getField().getZero();
  779.                 final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
  780.                                                                                            // for example removing derivatives
  781.                                                                                            // if T was DerivativeStructure
  782.                 cache.getNeighbors(aDate).forEach(entry -> {
  783.                     for (int i = 0; i < y.length; ++i) {
  784.                         y[i] = zero.add(entry.correction[i]);
  785.                     }
  786.                     interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
  787.                 });

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

  790.             } catch (TimeStampedCacheException tsce) {
  791.                 // this should never happen
  792.                 throw new OrekitInternalError(tsce);
  793.             }
  794.         }

  795.         /** {@inheritDoc} */
  796.         @Override
  797.         public List<TidalCorrectionEntry> generate(final AbsoluteDate existingDate, final AbsoluteDate date) {

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

  799.             if (existingDate == null) {

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

  805.             } else {

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

  808.                 AbsoluteDate t = existingDate;
  809.                 if (date.compareTo(t) > 0) {
  810.                     // forward generation
  811.                     do {
  812.                         t = t.shiftedBy(step);
  813.                         generated.add(new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  814.                     } while (t.compareTo(date) <= 0);
  815.                 } else {
  816.                     // backward generation
  817.                     do {
  818.                         t = t.shiftedBy(-step);
  819.                         generated.add(0, new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  820.                     } while (t.compareTo(date) >= 0);
  821.                 }
  822.             }

  823.             // return the generated transforms
  824.             return generated;

  825.         }
  826.     }

  827. }