EOPHistory.java

  1. /* Copyright 2002-2016 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 org.apache.commons.math3.analysis.interpolation.HermiteInterpolator;
  23. import org.orekit.errors.OrekitException;
  24. import org.orekit.errors.OrekitInternalError;
  25. import org.orekit.errors.OrekitMessages;
  26. import org.orekit.errors.TimeStampedCacheException;
  27. import org.orekit.time.AbsoluteDate;
  28. import org.orekit.time.TimeFunction;
  29. import org.orekit.time.TimeStamped;
  30. import org.orekit.utils.Constants;
  31. import org.orekit.utils.GenericTimeStampedCache;
  32. import org.orekit.utils.IERSConventions;
  33. import org.orekit.utils.ImmutableTimeStampedCache;
  34. import org.orekit.utils.OrekitConfiguration;
  35. import org.orekit.utils.TimeStampedCache;
  36. import org.orekit.utils.TimeStampedGenerator;

  37. /** This class loads any kind of Earth Orientation Parameter data throughout a large time range.
  38.  * @author Pascal Parraud
  39.  */
  40. public class EOPHistory implements Serializable {

  41.     /** Serializable UID. */
  42.     private static final long serialVersionUID = 20131010L;

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

  45.     /**
  46.      * If this history has any EOP data.
  47.      *
  48.      * @see #hasDataFor(AbsoluteDate)
  49.      */
  50.     private final boolean hasData;

  51.     /** EOP history entries. */
  52.     private final transient ImmutableTimeStampedCache<EOPEntry> cache;

  53.     /** IERS conventions to which EOP refers. */
  54.     private final IERSConventions conventions;

  55.     /** Correction to apply to EOP (may be null). */
  56.     private final transient TimeFunction<double[]> tidalCorrection;

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

  69.     /** Simple constructor.
  70.      * @param conventions IERS conventions to which EOP refers
  71.      * @param data the EOP data to use
  72.      * @param tidalCorrection correction to apply to EOP
  73.      * @exception OrekitException if tidal correction model cannot be loaded
  74.      */
  75.     private EOPHistory(final IERSConventions conventions,
  76.                          final Collection<EOPEntry> data,
  77.                          final TimeFunction<double[]> tidalCorrection)
  78.         throws OrekitException {
  79.         this.conventions      = conventions;
  80.         this.tidalCorrection  = tidalCorrection;
  81.         if (data.size() >= INTERPOLATION_POINTS) {
  82.             // enough data to interpolate
  83.             cache = new ImmutableTimeStampedCache<EOPEntry>(INTERPOLATION_POINTS, data);
  84.             hasData = true;
  85.         } else {
  86.             // not enough data to interpolate -> always use null correction
  87.             cache = ImmutableTimeStampedCache.emptyCache();
  88.             hasData = false;
  89.         }
  90.     }

  91.     /** Get non-interpolating version of the instance.
  92.      * @return non-interpolatig version of the instance
  93.      * @exception OrekitException if tidal correction model cannot be loaded
  94.      */
  95.     public EOPHistory getNonInterpolatingEOPHistory()
  96.         throws OrekitException {
  97.         return new EOPHistory(conventions, getEntries(), conventions.getEOPTidalCorrection());
  98.     }

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

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

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

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

  123.     /** Get the UT1-UTC value.
  124.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  125.      * @param date date at which the value is desired
  126.      * @return UT1-UTC in seconds (0 if date is outside covered range)
  127.      */
  128.     public double getUT1MinusUTC(final AbsoluteDate date) {
  129.         //check if there is data for date
  130.         if (!this.hasDataFor(date)) {
  131.             // no EOP data available for this date, we use a default 0.0 offset
  132.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[2];
  133.         }
  134.         //we have EOP data -> interpolate offset
  135.         try {
  136.             final List<EOPEntry> neighbors = getNeighbors(date);
  137.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  138.             final double firstDUT = neighbors.get(0).getUT1MinusUTC();
  139.             boolean beforeLeap = true;
  140.             for (final EOPEntry neighbor : neighbors) {
  141.                 final double dut;
  142.                 if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
  143.                     // there was a leap second between the entries
  144.                     dut = neighbor.getUT1MinusUTC() - 1.0;
  145.                     if (neighbor.getDate().compareTo(date) <= 0) {
  146.                         beforeLeap = false;
  147.                     }
  148.                 } else {
  149.                     dut = neighbor.getUT1MinusUTC();
  150.                 }
  151.                 interpolator.addSamplePoint(neighbor.getDate().durationFrom(date),
  152.                                             new double[] {
  153.                                                 dut
  154.                                             });
  155.             }
  156.             double interpolated = interpolator.value(0)[0];
  157.             if (tidalCorrection != null) {
  158.                 interpolated += tidalCorrection.value(date)[2];
  159.             }
  160.             return beforeLeap ? interpolated : interpolated + 1.0;
  161.         } catch (TimeStampedCacheException tce) {
  162.             //this should not happen because of date check above
  163.             throw new OrekitInternalError(tce);
  164.         }
  165.     }

  166.     /**
  167.      * Get the entries surrounding a central date.
  168.      * <p>
  169.      * See {@link #hasDataFor(AbsoluteDate)} to determine if the cache has data
  170.      * for {@code central} without throwing an exception.
  171.      *
  172.      * @param central central date
  173.      * @return array of cached entries surrounding specified date
  174.      * @exception TimeStampedCacheException if EOP data cannot be retrieved
  175.      */
  176.     protected List<EOPEntry> getNeighbors(final AbsoluteDate central) throws TimeStampedCacheException {
  177.         return cache.getNeighbors(central);
  178.     }

  179.     /** Get the LoD (Length of Day) value.
  180.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  181.      * @param date date at which the value is desired
  182.      * @return LoD in seconds (0 if date is outside covered range)
  183.      */
  184.     public double getLOD(final AbsoluteDate date) {
  185.         //check if there is data for date
  186.         if (!this.hasDataFor(date)) {
  187.             // no EOP data available for this date, we use a default null correction
  188.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[3];
  189.         }
  190.         //we have EOP data for date -> interpolate correction
  191.         try {
  192.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  193.             for (final EOPEntry entry : getNeighbors(date)) {
  194.                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  195.                                             new double[] {
  196.                                                 entry.getLOD()
  197.                                             });
  198.             }
  199.             double interpolated = interpolator.value(0)[0];
  200.             if (tidalCorrection != null) {
  201.                 interpolated += tidalCorrection.value(date)[3];
  202.             }
  203.             return interpolated;
  204.         } catch (TimeStampedCacheException tce) {
  205.             // this should not happen because of date check above
  206.             throw new OrekitInternalError(tce);
  207.         }
  208.     }

  209.     /** Get the pole IERS Reference Pole correction.
  210.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  211.      * @param date date at which the correction is desired
  212.      * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
  213.      * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
  214.      */
  215.     public PoleCorrection getPoleCorrection(final AbsoluteDate date) {
  216.         // check if there is data for date
  217.         if (!this.hasDataFor(date)) {
  218.             // no EOP data available for this date, we use a default null correction
  219.             if (tidalCorrection == null) {
  220.                 return PoleCorrection.NULL_CORRECTION;
  221.             } else {
  222.                 final double[] correction = tidalCorrection.value(date);
  223.                 return new PoleCorrection(correction[0], correction[1]);
  224.             }
  225.         }
  226.         //we have EOP data for date -> interpolate correction
  227.         try {
  228.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  229.             for (final EOPEntry entry : getNeighbors(date)) {
  230.                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  231.                                             new double[] {
  232.                                                 entry.getX(), entry.getY()
  233.                                             });
  234.             }
  235.             final double[] interpolated = interpolator.value(0);
  236.             if (tidalCorrection != null) {
  237.                 final double[] correction = tidalCorrection.value(date);
  238.                 interpolated[0] += correction[0];
  239.                 interpolated[1] += correction[1];
  240.             }
  241.             return new PoleCorrection(interpolated[0], interpolated[1]);
  242.         } catch (TimeStampedCacheException tce) {
  243.             // this should not happen because of date check above
  244.             throw new OrekitInternalError(tce);
  245.         }
  246.     }

  247.     /** Get the correction to the nutation parameters for equinox-based paradigm.
  248.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  249.      * @param date date at which the correction is desired
  250.      * @return nutation correction in longitude ΔΨ and in obliquity Δε
  251.      * (zero if date is outside covered range)
  252.      */
  253.     public double[] getEquinoxNutationCorrection(final AbsoluteDate date) {
  254.         // check if there is data for date
  255.         if (!this.hasDataFor(date)) {
  256.             // no EOP data available for this date, we use a default null correction
  257.             return new double[2];
  258.         }
  259.         //we have EOP data for date -> interpolate correction
  260.         try {
  261.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  262.             for (final EOPEntry entry : getNeighbors(date)) {
  263.                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  264.                                             new double[] {
  265.                                                 entry.getDdPsi(), entry.getDdEps()
  266.                                             });
  267.             }
  268.             return interpolator.value(0);
  269.         } catch (TimeStampedCacheException tce) {
  270.             // this should not happen because of date check above
  271.             throw new OrekitInternalError(tce);
  272.         }
  273.     }

  274.     /** Get the correction to the nutation parameters for Non-Rotating Origin paradigm.
  275.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  276.      * @param date date at which the correction is desired
  277.      * @return nutation correction in Celestial Intermediat Pole coordinates
  278.      * δX and δY (zero if date is outside covered range)
  279.      */
  280.     public double[] getNonRotatinOriginNutationCorrection(final AbsoluteDate date) {
  281.         // check if there is data for date
  282.         if (!this.hasDataFor(date)) {
  283.             // no EOP data available for this date, we use a default null correction
  284.             return new double[2];
  285.         }
  286.         //we have EOP data for date -> interpolate correction
  287.         try {
  288.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  289.             for (final EOPEntry entry : getNeighbors(date)) {
  290.                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  291.                                             new double[] {
  292.                                                 entry.getDx(), entry.getDy()
  293.                                             });
  294.             }
  295.             return interpolator.value(0);
  296.         } catch (TimeStampedCacheException tce) {
  297.             // this should not happen because of date check above
  298.             throw new OrekitInternalError(tce);
  299.         }
  300.     }

  301.     /** Check Earth orientation parameters continuity.
  302.      * @param maxGap maximal allowed gap between entries (in seconds)
  303.      * @exception OrekitException if there are holes in the data sequence
  304.      */
  305.     public void checkEOPContinuity(final double maxGap) throws OrekitException {
  306.         TimeStamped preceding = null;
  307.         for (final TimeStamped current : this.cache.getAll()) {

  308.             // compare the dates of preceding and current entries
  309.             if ((preceding != null) && ((current.getDate().durationFrom(preceding.getDate())) > maxGap)) {
  310.                 throw new OrekitException(OrekitMessages.MISSING_EARTH_ORIENTATION_PARAMETERS_BETWEEN_DATES,
  311.                                           preceding.getDate(), current.getDate());
  312.             }

  313.             // prepare next iteration
  314.             preceding = current;

  315.         }
  316.     }

  317.     /**
  318.      * Check if the cache has data for the given date using
  319.      * {@link #getStartDate()} and {@link #getEndDate()}.
  320.      *
  321.      * @param date the requested date
  322.      * @return true if the {@link #cache} has data for the requested date, false
  323.      *         otherwise.
  324.      */
  325.     protected boolean hasDataFor(final AbsoluteDate date) {
  326.         /*
  327.          * when there is no EOP data, short circuit getStartDate, which will
  328.          * throw an exception
  329.          */
  330.         return this.hasData && this.getStartDate().compareTo(date) <= 0 &&
  331.                date.compareTo(this.getEndDate()) <= 0;
  332.     }

  333.     /** Get a non-modifiable view of the EOP entries.
  334.      * @return non-modifiable view of the EOP entries
  335.      */
  336.     List<EOPEntry> getEntries() {
  337.         return cache.getAll();
  338.     }

  339.     /** Replace the instance with a data transfer object for serialization.
  340.      * <p>
  341.      * This intermediate class serializes only the frame key.
  342.      * </p>
  343.      * @return data transfer object that will be serialized
  344.      */
  345.     private Object writeReplace() {
  346.         return new DataTransferObject(conventions, getEntries(), tidalCorrection == null);
  347.     }

  348.     /** Internal class used only for serialization. */
  349.     private static class DataTransferObject implements Serializable {

  350.         /** Serializable UID. */
  351.         private static final long serialVersionUID = 20131010L;

  352.         /** IERS conventions. */
  353.         private final IERSConventions conventions;

  354.         /** EOP entries. */
  355.         private final List<EOPEntry> entries;

  356.         /** Indicator for simple interpolation without tidal effects. */
  357.         private final boolean simpleEOP;

  358.         /** Simple constructor.
  359.          * @param conventions IERS conventions to which EOP refers
  360.          * @param entries the EOP data to use
  361.          * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  362.          */
  363.         DataTransferObject(final IERSConventions conventions,
  364.                                   final List<EOPEntry> entries,
  365.                                   final boolean simpleEOP) {
  366.             this.conventions = conventions;
  367.             this.entries     = entries;
  368.             this.simpleEOP   = simpleEOP;
  369.         }

  370.         /** Replace the deserialized data transfer object with a {@link EOPHistory}.
  371.          * @return replacement {@link EOPHistory}
  372.          */
  373.         private Object readResolve() {
  374.             try {
  375.                 // retrieve a managed frame
  376.                 return new EOPHistory(conventions, entries, simpleEOP);
  377.             } catch (OrekitException oe) {
  378.                 throw new OrekitInternalError(oe);
  379.             }
  380.         }

  381.     }

  382.     /** Internal class for caching tidal correction. */
  383.     private static class TidalCorrectionEntry implements TimeStamped {

  384.         /** Entry date. */
  385.         private final AbsoluteDate date;

  386.         /** Correction. */
  387.         private final double[] correction;

  388.         /** Simple constructor.
  389.          * @param date entry date
  390.          * @param correction correction on the EOP parameters (xp, yp, ut1, lod)
  391.          */
  392.         TidalCorrectionEntry(final AbsoluteDate date, final double[] correction) {
  393.             this.date       = date;
  394.             this.correction = correction;
  395.         }

  396.         /** {@inheritDoc} */
  397.         @Override
  398.         public AbsoluteDate getDate() {
  399.             return date;
  400.         }

  401.     }

  402.     /** Local generator for thread-safe cache. */
  403.     private static class CachedCorrection
  404.         implements TimeFunction<double[]>, TimeStampedGenerator<TidalCorrectionEntry> {

  405.         /** Correction to apply to EOP (may be null). */
  406.         private final TimeFunction<double[]> tidalCorrection;

  407.         /** Step between generated entries. */
  408.         private final double step;

  409.         /** Tidal corrections entries cache. */
  410.         private final TimeStampedCache<TidalCorrectionEntry> cache;

  411.         /** Simple constructor.
  412.          * @param tidalCorrection function computing the tidal correction
  413.          */
  414.         CachedCorrection(final TimeFunction<double[]> tidalCorrection) {
  415.             this.step            = 60 * 60;
  416.             this.tidalCorrection = tidalCorrection;
  417.             this.cache           =
  418.                 new GenericTimeStampedCache<TidalCorrectionEntry>(8,
  419.                                                                   OrekitConfiguration.getCacheSlotsNumber(),
  420.                                                                   Constants.JULIAN_DAY * 30,
  421.                                                                   Constants.JULIAN_DAY,
  422.                                                                   this,
  423.                                                                   TidalCorrectionEntry.class);
  424.         }

  425.         /** {@inheritDoc} */
  426.         @Override
  427.         public double[] value(final AbsoluteDate date) {
  428.             try {
  429.                 // set up an interpolator
  430.                 final HermiteInterpolator interpolator = new HermiteInterpolator();
  431.                 for (final TidalCorrectionEntry entry : cache.getNeighbors(date)) {
  432.                     interpolator.addSamplePoint(entry.date.durationFrom(date), entry.correction);
  433.                 }

  434.                 // interpolate to specified date
  435.                 return interpolator.value(0.0);
  436.             } catch (TimeStampedCacheException tsce) {
  437.                 // this should never happen
  438.                 throw new OrekitInternalError(tsce);
  439.             }
  440.         }

  441.         /** {@inheritDoc} */
  442.         @Override
  443.         public List<TidalCorrectionEntry> generate(final TidalCorrectionEntry existing, final AbsoluteDate date) {

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

  445.             if (existing == null) {

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

  451.             } else {

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

  454.                 AbsoluteDate t = existing.getDate();
  455.                 if (date.compareTo(t) > 0) {
  456.                     // forward generation
  457.                     do {
  458.                         t = t.shiftedBy(step);
  459.                         generated.add(new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  460.                     } while (t.compareTo(date) <= 0);
  461.                 } else {
  462.                     // backward generation
  463.                     do {
  464.                         t = t.shiftedBy(-step);
  465.                         generated.add(0, new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  466.                     } while (t.compareTo(date) >= 0);
  467.                 }
  468.             }

  469.             // return the generated transforms
  470.             return generated;

  471.         }
  472.     }

  473. }