EOPHistory.java

  1. /* Copyright 2002-2020 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.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.annotation.DefaultDataContext;
  31. import org.orekit.data.DataContext;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitInternalError;
  34. import org.orekit.errors.OrekitMessages;
  35. import org.orekit.errors.TimeStampedCacheException;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.time.FieldAbsoluteDate;
  38. import org.orekit.time.TimeScales;
  39. import org.orekit.time.TimeStamped;
  40. import org.orekit.time.TimeVectorFunction;
  41. import org.orekit.utils.Constants;
  42. import org.orekit.utils.GenericTimeStampedCache;
  43. import org.orekit.utils.IERSConventions;
  44. import org.orekit.utils.ImmutableTimeStampedCache;
  45. import org.orekit.utils.OrekitConfiguration;
  46. import org.orekit.utils.TimeStampedCache;
  47. import org.orekit.utils.TimeStampedGenerator;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  198.     }

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

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

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

  226.     }

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

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

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

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

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

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

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

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

  278.     }

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

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

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

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

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

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

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

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

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

  330.     }

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

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

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

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

  360.     }

  361.     /** Get the LoD (Length of Day) value.
  362.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  363.      * @param date date at which the value is desired
  364.      * @param <T> type of the filed elements
  365.      * @return LoD in seconds (0 if date is outside covered range)
  366.      * @since 9.0
  367.      */
  368.     public <T extends RealFieldElement<T>> T getLOD(final FieldAbsoluteDate<T> date) {

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

  370.         // check if there is data for date
  371.         if (!this.hasDataFor(aDate)) {
  372.             // no EOP data available for this date, we use a default null correction
  373.             return (tidalCorrection == null) ? date.getField().getZero() : tidalCorrection.value(date)[3];
  374.         }

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

  380.         return interpolated;

  381.     }

  382.     /** Get the pole IERS Reference Pole correction.
  383.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  384.      * @param date date at which the correction is desired
  385.      * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
  386.      * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
  387.      */
  388.     public PoleCorrection getPoleCorrection(final AbsoluteDate date) {

  389.         // check if there is data for date
  390.         if (!this.hasDataFor(date)) {
  391.             // no EOP data available for this date, we use a default null correction
  392.             if (tidalCorrection == null) {
  393.                 return PoleCorrection.NULL_CORRECTION;
  394.             } else {
  395.                 final double[] correction = tidalCorrection.value(date);
  396.                 return new PoleCorrection(correction[0], correction[1]);
  397.             }
  398.         }

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

  407.     }

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

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

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

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

  435.     }

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

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

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

  450.     }

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

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

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

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

  467.     }

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

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

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

  482.     }

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

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

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

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

  499.     }

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

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

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

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

  519.     }

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

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

  531.             // prepare next iteration
  532.             preceding = current;

  533.         }
  534.     }

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

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

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

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

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

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

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

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

  681.         /** Serializable UID. */
  682.         private static final long serialVersionUID = 20131010L;

  683.         /** IERS conventions. */
  684.         private final IERSConventions conventions;

  685.         /** EOP entries. */
  686.         private final List<EOPEntry> entries;

  687.         /** Indicator for simple interpolation without tidal effects. */
  688.         private final boolean simpleEOP;

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

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

  712.     }

  713.     /** Internal class for caching tidal correction. */
  714.     private static class TidalCorrectionEntry implements TimeStamped {

  715.         /** Entry date. */
  716.         private final AbsoluteDate date;

  717.         /** Correction. */
  718.         private final double[] correction;

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

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

  732.     }

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

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

  738.         /** Step between generated entries. */
  739.         private final double step;

  740.         /** Tidal corrections entries cache. */
  741.         private final TimeStampedCache<TidalCorrectionEntry> cache;

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

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

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

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

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

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

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

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

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

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

  797.             if (existingDate == null) {

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

  803.             } else {

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

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

  821.             // return the generated transforms
  822.             return generated;

  823.         }
  824.     }

  825. }