EOPHistory.java

  1. /* Copyright 2002-2013 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.Collection;
  20. import java.util.List;

  21. import org.apache.commons.math3.analysis.interpolation.HermiteInterpolator;
  22. import org.orekit.errors.OrekitException;
  23. import org.orekit.errors.OrekitMessages;
  24. import org.orekit.errors.TimeStampedCacheException;
  25. import org.orekit.time.AbsoluteDate;
  26. import org.orekit.time.TimeFunction;
  27. import org.orekit.time.TimeStamped;
  28. import org.orekit.utils.IERSConventions;
  29. import org.orekit.utils.ImmutableTimeStampedCache;

  30. /** This class loads any kind of Earth Orientation Parameter data throughout a large time range.
  31.  * @author Pascal Parraud
  32.  */
  33. public class EOPHistory implements Serializable {

  34.     /** Serializable UID. */
  35.     private static final long serialVersionUID = 20131010L;

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

  38.     /**
  39.      * If this history has any EOP data.
  40.      *
  41.      * @see #hasDataFor(AbsoluteDate)
  42.      */
  43.     private final boolean hasData;

  44.     /** EOP history entries. */
  45.     private final transient ImmutableTimeStampedCache<EOPEntry> cache;

  46.     /** IERS conventions to which EOP refers. */
  47.     private final IERSConventions conventions;

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

  50.     /** Simple constructor.
  51.      * @param conventions IERS conventions to which EOP refers
  52.      * @param data the EOP data to use
  53.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  54.      * @exception OrekitException if tidal correction model cannot be loaded
  55.      */
  56.     protected EOPHistory(final IERSConventions conventions,
  57.                          final Collection<EOPEntry> data,
  58.                          final boolean simpleEOP)
  59.         throws OrekitException {
  60.         this.conventions = conventions;
  61.         tidalCorrection = simpleEOP ? null : conventions.getEOPTidalCorrection();
  62.         if (data.size() >= INTERPOLATION_POINTS) {
  63.             // enough data to interpolate
  64.             cache = new ImmutableTimeStampedCache<EOPEntry>(INTERPOLATION_POINTS, data);
  65.             hasData = true;
  66.         } else {
  67.             // not enough data to interpolate -> always use null correction
  68.             cache = ImmutableTimeStampedCache.emptyCache();
  69.             hasData = false;
  70.         }
  71.     }

  72.     /** Get the IERS conventions to which these EOP apply.
  73.      * @return IERS conventions to which these EOP apply
  74.      */
  75.     public IERSConventions getConventions() {
  76.         return conventions;
  77.     }

  78.     /** Get the date of the first available Earth Orientation Parameters.
  79.      * @return the start date of the available data
  80.      */
  81.     public AbsoluteDate getStartDate() {
  82.         return this.cache.getEarliest().getDate();
  83.     }

  84.     /** Get the date of the last available Earth Orientation Parameters.
  85.      * @return the end date of the available data
  86.      */
  87.     public AbsoluteDate getEndDate() {
  88.         return this.cache.getLatest().getDate();
  89.     }

  90.     /** Get the UT1-UTC value.
  91.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  92.      * @param date date at which the value is desired
  93.      * @return UT1-UTC in seconds (0 if date is outside covered range)
  94.      */
  95.     public double getUT1MinusUTC(final AbsoluteDate date) {
  96.         //check if there is data for date
  97.         if (!this.hasDataFor(date)) {
  98.             // no EOP data available for this date, we use a default 0.0 offset
  99.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[2];
  100.         }
  101.         //we have EOP data -> interpolate offset
  102.         try {
  103.             final List<EOPEntry> neighbors = getNeighbors(date);
  104.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  105.             final double firstDUT = neighbors.get(0).getUT1MinusUTC();
  106.             boolean beforeLeap = true;
  107.             for (final EOPEntry neighbor : neighbors) {
  108.                 final double dut;
  109.                 if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
  110.                     // there was a leap second between the entries
  111.                     dut = neighbor.getUT1MinusUTC() - 1.0;
  112.                     if (neighbor.getDate().compareTo(date) <= 0) {
  113.                         beforeLeap = false;
  114.                     }
  115.                 } else {
  116.                     dut = neighbor.getUT1MinusUTC();
  117.                 }
  118.                 interpolator.addSamplePoint(neighbor.getDate().durationFrom(date),
  119.                                             new double[] {
  120.                                                 dut
  121.                                             });
  122.             }
  123.             double interpolated = interpolator.value(0)[0];
  124.             if (tidalCorrection != null) {
  125.                 interpolated += tidalCorrection.value(date)[2];
  126.             }
  127.             return beforeLeap ? interpolated : interpolated + 1.0;
  128.         } catch (TimeStampedCacheException tce) {
  129.             //this should not happen because of date check above
  130.             throw OrekitException.createInternalError(tce);
  131.         }
  132.     }

  133.     /**
  134.      * Get the entries surrounding a central date.
  135.      * <p>
  136.      * See {@link #hasDataFor(AbsoluteDate)} to determine if the cache has data
  137.      * for {@code central} without throwing an exception.
  138.      *
  139.      * @param central central date
  140.      * @return array of cached entries surrounding specified date
  141.      * @exception TimeStampedCacheException if EOP data cannot be retrieved
  142.      */
  143.     protected List<EOPEntry> getNeighbors(final AbsoluteDate central) throws TimeStampedCacheException {
  144.         return cache.getNeighbors(central);
  145.     }

  146.     /** Get the LoD (Length of Day) value.
  147.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  148.      * @param date date at which the value is desired
  149.      * @return LoD in seconds (0 if date is outside covered range)
  150.      */
  151.     public double getLOD(final AbsoluteDate date) {
  152.         //check if there is data for date
  153.         if (!this.hasDataFor(date)) {
  154.             // no EOP data available for this date, we use a default null correction
  155.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[3];
  156.         }
  157.         //we have EOP data for date -> interpolate correction
  158.         try {
  159.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  160.             for (final EOPEntry entry : getNeighbors(date)) {
  161.                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  162.                                             new double[] {
  163.                                                 entry.getLOD()
  164.                                             });
  165.             }
  166.             double interpolated = interpolator.value(0)[0];
  167.             if (tidalCorrection != null) {
  168.                 interpolated += tidalCorrection.value(date)[3];
  169.             }
  170.             return interpolated;
  171.         } catch (TimeStampedCacheException tce) {
  172.             // this should not happen because of date check above
  173.             throw OrekitException.createInternalError(tce);
  174.         }
  175.     }

  176.     /** Get the pole IERS Reference Pole correction.
  177.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  178.      * @param date date at which the correction is desired
  179.      * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
  180.      * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
  181.      */
  182.     public PoleCorrection getPoleCorrection(final AbsoluteDate date) {
  183.         // check if there is data for date
  184.         if (!this.hasDataFor(date)) {
  185.             // no EOP data available for this date, we use a default null correction
  186.             if (tidalCorrection == null) {
  187.                 return PoleCorrection.NULL_CORRECTION;
  188.             } else {
  189.                 final double[] correction = tidalCorrection.value(date);
  190.                 return new PoleCorrection(correction[0], correction[1]);
  191.             }
  192.         }
  193.         //we have EOP data for date -> interpolate correction
  194.         try {
  195.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  196.             for (final EOPEntry entry : getNeighbors(date)) {
  197.                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  198.                                             new double[] {
  199.                                                 entry.getX(), entry.getY()
  200.                                             });
  201.             }
  202.             final double[] interpolated = interpolator.value(0);
  203.             if (tidalCorrection != null) {
  204.                 final double[] correction = tidalCorrection.value(date);
  205.                 interpolated[0] += correction[0];
  206.                 interpolated[1] += correction[1];
  207.             }
  208.             return new PoleCorrection(interpolated[0], interpolated[1]);
  209.         } catch (TimeStampedCacheException tce) {
  210.             // this should not happen because of date check above
  211.             throw OrekitException.createInternalError(tce);
  212.         }
  213.     }

  214.     /** Get the correction to the nutation parameters for equinox-based paradigm.
  215.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  216.      * @param date date at which the correction is desired
  217.      * @return nutation correction in longitude &Delta;&Psi; and in obliquity &Delta;&epsilon;
  218.      * (zero if date is outside covered range)
  219.      */
  220.     public double[] getEquinoxNutationCorrection(final AbsoluteDate date) {
  221.         // check if there is data for date
  222.         if (!this.hasDataFor(date)) {
  223.             // no EOP data available for this date, we use a default null correction
  224.             return new double[2];
  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.getDdPsi(), entry.getDdEps()
  233.                                             });
  234.             }
  235.             return interpolator.value(0);
  236.         } catch (TimeStampedCacheException tce) {
  237.             // this should not happen because of date check above
  238.             throw OrekitException.createInternalError(tce);
  239.         }
  240.     }

  241.     /** Get the correction to the nutation parameters for Non-Rotating Origin paradigm.
  242.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  243.      * @param date date at which the correction is desired
  244.      * @return nutation correction in Celestial Intermediat Pole coordinates
  245.      * &delta;X and &delta;Y (zero if date is outside covered range)
  246.      */
  247.     public double[] getNonRotatinOriginNutationCorrection(final AbsoluteDate date) {
  248.         // check if there is data for date
  249.         if (!this.hasDataFor(date)) {
  250.             // no EOP data available for this date, we use a default null correction
  251.             return new double[2];
  252.         }
  253.         //we have EOP data for date -> interpolate correction
  254.         try {
  255.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  256.             for (final EOPEntry entry : getNeighbors(date)) {
  257.                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  258.                                             new double[] {
  259.                                                 entry.getDx(), entry.getDy()
  260.                                             });
  261.             }
  262.             return interpolator.value(0);
  263.         } catch (TimeStampedCacheException tce) {
  264.             // this should not happen because of date check above
  265.             throw OrekitException.createInternalError(tce);
  266.         }
  267.     }

  268.     /** Check Earth orientation parameters continuity.
  269.      * @param maxGap maximal allowed gap between entries (in seconds)
  270.      * @exception OrekitException if there are holes in the data sequence
  271.      */
  272.     public void checkEOPContinuity(final double maxGap) throws OrekitException {
  273.         TimeStamped preceding = null;
  274.         for (final TimeStamped current : this.cache.getAll()) {

  275.             // compare the dates of preceding and current entries
  276.             if ((preceding != null) && ((current.getDate().durationFrom(preceding.getDate())) > maxGap)) {
  277.                 throw new OrekitException(OrekitMessages.MISSING_EARTH_ORIENTATION_PARAMETERS_BETWEEN_DATES,
  278.                                           preceding.getDate(), current.getDate());
  279.             }

  280.             // prepare next iteration
  281.             preceding = current;

  282.         }
  283.     }

  284.     /**
  285.      * Check if the cache has data for the given date using
  286.      * {@link #getStartDate()} and {@link #getEndDate()}.
  287.      *
  288.      * @param date the requested date
  289.      * @return true if the {@link #cache} has data for the requested date, false
  290.      *         otherwise.
  291.      */
  292.     protected boolean hasDataFor(final AbsoluteDate date) {
  293.         /*
  294.          * when there is no EOP data, short circuit getStartDate, which will
  295.          * throw an exception
  296.          */
  297.         return this.hasData && this.getStartDate().compareTo(date) <= 0 &&
  298.                date.compareTo(this.getEndDate()) <= 0;
  299.     }

  300.     /** Get a non-modifiable view of the EOP entries.
  301.      * @return non-modifiable view of the EOP entries
  302.      */
  303.     List<EOPEntry> getEntries() {
  304.         return cache.getAll();
  305.     }

  306.     /** Replace the instance with a data transfer object for serialization.
  307.      * <p>
  308.      * This intermediate class serializes only the frame key.
  309.      * </p>
  310.      * @return data transfer object that will be serialized
  311.      */
  312.     private Object writeReplace() {
  313.         return new DataTransferObject(conventions, getEntries(), tidalCorrection == null);
  314.     }

  315.     /** Internal class used only for serialization. */
  316.     private static class DataTransferObject implements Serializable {

  317.         /** Serializable UID. */
  318.         private static final long serialVersionUID = 20131010L;

  319.         /** IERS conventions. */
  320.         private final IERSConventions conventions;

  321.         /** EOP entries. */
  322.         private final List<EOPEntry> entries;

  323.         /** Indicator for simple interpolation without tidal effects. */
  324.         private final boolean simpleEOP;

  325.         /** Simple constructor.
  326.          * @param conventions IERS conventions to which EOP refers
  327.          * @param entries the EOP data to use
  328.          * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  329.          */
  330.         public DataTransferObject(final IERSConventions conventions,
  331.                                   final List<EOPEntry> entries,
  332.                                   final boolean simpleEOP) {
  333.             this.conventions = conventions;
  334.             this.entries     = entries;
  335.             this.simpleEOP   = simpleEOP;
  336.         }

  337.         /** Replace the deserialized data transfer object with a {@link EOPHistory}.
  338.          * @return replacement {@link EOPHistory}
  339.          */
  340.         private Object readResolve() {
  341.             try {
  342.                 // retrieve a managed frame
  343.                 return new EOPHistory(conventions, entries, simpleEOP);
  344.             } catch (OrekitException oe) {
  345.                 throw OrekitException.createInternalError(oe);
  346.             }
  347.         }

  348.     }

  349. }