EOPHistory.java
- /* Copyright 2002-2025 CS GROUP
- * Licensed to CS GROUP (CS) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * CS licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.orekit.frames;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.List;
- import java.util.Optional;
- import java.util.function.BiFunction;
- import java.util.function.Consumer;
- import java.util.function.Function;
- import java.util.stream.Stream;
- import org.hipparchus.CalculusFieldElement;
- import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
- import org.hipparchus.analysis.interpolation.HermiteInterpolator;
- import org.hipparchus.util.FastMath;
- import org.hipparchus.util.MathArrays;
- import org.orekit.annotation.DefaultDataContext;
- import org.orekit.data.DataContext;
- import org.orekit.errors.OrekitException;
- import org.orekit.errors.OrekitInternalError;
- import org.orekit.errors.OrekitMessages;
- import org.orekit.errors.TimeStampedCacheException;
- import org.orekit.time.AbsoluteDate;
- import org.orekit.time.FieldAbsoluteDate;
- import org.orekit.time.TimeScales;
- import org.orekit.time.TimeStamped;
- import org.orekit.time.TimeVectorFunction;
- import org.orekit.utils.Constants;
- import org.orekit.utils.GenericTimeStampedCache;
- import org.orekit.utils.IERSConventions;
- import org.orekit.utils.ImmutableTimeStampedCache;
- import org.orekit.utils.OrekitConfiguration;
- import org.orekit.utils.TimeStampedCache;
- import org.orekit.utils.TimeStampedGenerator;
- /** This class loads any kind of Earth Orientation Parameter data throughout a large time range.
- * @author Pascal Parraud
- * @author Evan Ward
- */
- public class EOPHistory {
- /** Default interpolation degree.
- * @since 12.0
- */
- public static final int DEFAULT_INTERPOLATION_DEGREE = 3;
- /** Interpolation degree.
- * @since 12.0
- */
- private final int interpolationDegree;
- /**
- * If this history has any EOP data.
- *
- * @see #hasDataFor(AbsoluteDate)
- */
- private final boolean hasData;
- /** EOP history entries. */
- private final transient ImmutableTimeStampedCache<EOPEntry> cache;
- /** IERS conventions to which EOP refers. */
- private final IERSConventions conventions;
- /** Correction to apply to EOP (may be null). */
- private final transient TimeVectorFunction tidalCorrection;
- /** Time scales to use when computing corrections. */
- private final transient TimeScales timeScales;
- /** Simple constructor.
- *
- * <p>This method uses the {@link DataContext#getDefault() default data context}.
- *
- * @param conventions IERS conventions to which EOP refers
- * @param interpolationDegree interpolation degree (must be of the form 4k-1)
- * @param data the EOP data to use
- * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
- * @see #EOPHistory(IERSConventions, int, Collection, boolean, TimeScales)
- */
- @DefaultDataContext
- protected EOPHistory(final IERSConventions conventions,
- final int interpolationDegree,
- final Collection<? extends EOPEntry> data,
- final boolean simpleEOP) {
- this(conventions, interpolationDegree, data, simpleEOP, DataContext.getDefault().getTimeScales());
- }
- /** Simple constructor.
- * @param conventions IERS conventions to which EOP refers
- * @param interpolationDegree interpolation degree (must be of the form 4k-1)
- * @param data the EOP data to use
- * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
- * @param timeScales to use when computing EOP corrections.
- * @since 10.1
- */
- public EOPHistory(final IERSConventions conventions,
- final int interpolationDegree,
- final Collection<? extends EOPEntry> data,
- final boolean simpleEOP,
- final TimeScales timeScales) {
- this(conventions, interpolationDegree, data,
- simpleEOP ? null : new CachedCorrection(conventions.getEOPTidalCorrection(timeScales)),
- timeScales);
- }
- /** Simple constructor.
- * @param conventions IERS conventions to which EOP refers
- * @param interpolationDegree interpolation degree (must be of the form 4k-1)
- * @param data the EOP data to use
- * @param tidalCorrection correction to apply to EOP
- * @param timeScales to use when computing EOP corrections
- * @since 12
- */
- private EOPHistory(final IERSConventions conventions,
- final int interpolationDegree,
- final Collection<? extends EOPEntry> data,
- final TimeVectorFunction tidalCorrection,
- final TimeScales timeScales) {
- // check interpolation degree is 4k-1
- final int k = (interpolationDegree + 1) / 4;
- if (interpolationDegree != 4 * k - 1) {
- throw new OrekitException(OrekitMessages.WRONG_EOP_INTERPOLATION_DEGREE, interpolationDegree);
- }
- this.conventions = conventions;
- this.interpolationDegree = interpolationDegree;
- this.tidalCorrection = tidalCorrection;
- this.timeScales = timeScales;
- if (data.size() >= 1) {
- // enough data to interpolate
- if (missSomeDerivatives(data)) {
- // we need to estimate the missing derivatives
- final ImmutableTimeStampedCache<EOPEntry> rawCache =
- new ImmutableTimeStampedCache<>(FastMath.min(interpolationDegree + 1, data.size()), data);
- final List<EOPEntry>fixedData = new ArrayList<>();
- for (final EOPEntry entry : rawCache.getAll()) {
- fixedData.add(fixDerivatives(entry, rawCache));
- }
- cache = new ImmutableTimeStampedCache<>(FastMath.min(interpolationDegree + 1, fixedData.size()), fixedData);
- } else {
- cache = new ImmutableTimeStampedCache<>(FastMath.min(interpolationDegree + 1, data.size()), data);
- }
- hasData = true;
- } else {
- // not enough data to interpolate -> always use null correction
- cache = ImmutableTimeStampedCache.emptyCache();
- hasData = false;
- }
- }
- /**
- * Determine if this history uses simplified EOP corrections.
- *
- * @return {@code true} if tidal corrections are ignored, {@code false} otherwise.
- */
- public boolean isSimpleEop() {
- return tidalCorrection == null;
- }
- /** Get interpolation degree.
- * @return interpolation degree
- * @since 12.0
- */
- public int getInterpolationDegree() {
- return interpolationDegree;
- }
- /**
- * Get the time scales used in computing EOP corrections.
- *
- * @return set of time scales.
- * @since 10.1
- */
- public TimeScales getTimeScales() {
- return timeScales;
- }
- /** Get version of the instance that does not cache tidal correction.
- * @return version of the instance that does not cache tidal correction
- * @since 12.0
- */
- public EOPHistory getEOPHistoryWithoutCachedTidalCorrection() {
- return new EOPHistory(conventions, interpolationDegree, getEntries(),
- conventions.getEOPTidalCorrection(timeScales),
- timeScales);
- }
- /** Check if the instance caches tidal corrections.
- * @return true if the instance caches tidal corrections
- * @since 12.0
- */
- public boolean cachesTidalCorrection() {
- return tidalCorrection instanceof CachedCorrection;
- }
- /** Get the IERS conventions to which these EOP apply.
- * @return IERS conventions to which these EOP apply
- */
- public IERSConventions getConventions() {
- return conventions;
- }
- /** Get the date of the first available Earth Orientation Parameters.
- * @return the start date of the available data
- */
- public AbsoluteDate getStartDate() {
- return this.cache.getEarliest().getDate();
- }
- /** Get the date of the last available Earth Orientation Parameters.
- * @return the end date of the available data
- */
- public AbsoluteDate getEndDate() {
- return this.cache.getLatest().getDate();
- }
- /** Get the UT1-UTC value.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the value is desired
- * @return UT1-UTC in seconds (0 if date is outside covered range)
- */
- public double getUT1MinusUTC(final AbsoluteDate date) {
- //check if there is data for date
- if (!this.hasDataFor(date)) {
- // no EOP data available for this date, we use a default 0.0 offset
- return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[2];
- }
- // we have EOP data -> interpolate offset
- try {
- final DUT1Interpolator interpolator = new DUT1Interpolator(date);
- getNeighbors(date, interpolationDegree + 1).forEach(interpolator);
- double interpolated = interpolator.getInterpolated();
- if (tidalCorrection != null) {
- interpolated += tidalCorrection.value(date)[2];
- }
- return interpolated;
- } catch (TimeStampedCacheException tce) {
- //this should not happen because of date check above
- throw new OrekitInternalError(tce);
- }
- }
- /** Get the UT1-UTC value.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the value is desired
- * @param <T> type of the field elements
- * @return UT1-UTC in seconds (0 if date is outside covered range)
- * @since 9.0
- */
- public <T extends CalculusFieldElement<T>> T getUT1MinusUTC(final FieldAbsoluteDate<T> date) {
- //check if there is data for date
- final AbsoluteDate absDate = date.toAbsoluteDate();
- if (!this.hasDataFor(absDate)) {
- // no EOP data available for this date, we use a default 0.0 offset
- return (tidalCorrection == null) ? date.getField().getZero() : tidalCorrection.value(date)[2];
- }
- // we have EOP data -> interpolate offset
- try {
- final FieldDUT1Interpolator<T> interpolator = new FieldDUT1Interpolator<>(date, absDate);
- getNeighbors(absDate, interpolationDegree + 1).forEach(interpolator);
- T interpolated = interpolator.getInterpolated();
- if (tidalCorrection != null) {
- interpolated = interpolated.add(tidalCorrection.value(date)[2]);
- }
- return interpolated;
- } catch (TimeStampedCacheException tce) {
- //this should not happen because of date check above
- throw new OrekitInternalError(tce);
- }
- }
- /** Local class for DUT1 interpolation, crossing leaps safely. */
- private static class DUT1Interpolator implements Consumer<EOPEntry> {
- /** DUT at first entry. */
- private double firstDUT;
- /** Indicator for dates just before a leap occurring during the interpolation sample. */
- private boolean beforeLeap;
- /** Interpolator to use. */
- private final HermiteInterpolator interpolator;
- /** Interpolation date. */
- private AbsoluteDate date;
- /** Simple constructor.
- * @param date interpolation date
- */
- DUT1Interpolator(final AbsoluteDate date) {
- this.firstDUT = Double.NaN;
- this.beforeLeap = true;
- this.interpolator = new HermiteInterpolator();
- this.date = date;
- }
- /** {@inheritDoc} */
- @Override
- public void accept(final EOPEntry neighbor) {
- if (Double.isNaN(firstDUT)) {
- firstDUT = neighbor.getUT1MinusUTC();
- }
- final double dut;
- if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
- // there was a leap second between the entries
- dut = neighbor.getUT1MinusUTC() - 1.0;
- // UTCScale considers the discontinuity to occur at the start of the leap
- // second so this code must use the same convention. EOP entries are time
- // stamped at midnight UTC so 1 second before is the start of the leap
- // second.
- if (neighbor.getDate().shiftedBy(-1).compareTo(date) <= 0) {
- beforeLeap = false;
- }
- } else {
- dut = neighbor.getUT1MinusUTC();
- }
- interpolator.addSamplePoint(neighbor.getDate().durationFrom(date),
- new double[] {
- dut
- });
- }
- /** Get the interpolated value.
- * @return interpolated value
- */
- public double getInterpolated() {
- final double interpolated = interpolator.value(0)[0];
- return beforeLeap ? interpolated : interpolated + 1.0;
- }
- }
- /** Local class for DUT1 interpolation, crossing leaps safely. */
- private static class FieldDUT1Interpolator<T extends CalculusFieldElement<T>> implements Consumer<EOPEntry> {
- /** DUT at first entry. */
- private double firstDUT;
- /** Indicator for dates just before a leap occurring during the interpolation sample. */
- private boolean beforeLeap;
- /** Interpolator to use. */
- private final FieldHermiteInterpolator<T> interpolator;
- /** Interpolation date. */
- private FieldAbsoluteDate<T> date;
- /** Interpolation date. */
- private AbsoluteDate absDate;
- /** Simple constructor.
- * @param date interpolation date
- * @param absDate interpolation date
- */
- FieldDUT1Interpolator(final FieldAbsoluteDate<T> date, final AbsoluteDate absDate) {
- this.firstDUT = Double.NaN;
- this.beforeLeap = true;
- this.interpolator = new FieldHermiteInterpolator<>();
- this.date = date;
- this.absDate = absDate;
- }
- /** {@inheritDoc} */
- @Override
- public void accept(final EOPEntry neighbor) {
- if (Double.isNaN(firstDUT)) {
- firstDUT = neighbor.getUT1MinusUTC();
- }
- final double dut;
- if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
- // there was a leap second between the entries
- dut = neighbor.getUT1MinusUTC() - 1.0;
- if (neighbor.getDate().compareTo(absDate) <= 0) {
- beforeLeap = false;
- }
- } else {
- dut = neighbor.getUT1MinusUTC();
- }
- final T[] array = MathArrays.buildArray(date.getField(), 1);
- array[0] = date.getField().getZero().newInstance(dut);
- interpolator.addSamplePoint(date.durationFrom(neighbor.getDate()).negate(),
- array);
- }
- /** Get the interpolated value.
- * @return interpolated value
- */
- public T getInterpolated() {
- final T interpolated = interpolator.value(date.getField().getZero())[0];
- return beforeLeap ? interpolated : interpolated.add(1.0);
- }
- }
- /**
- * Get the entries surrounding a central date.
- * <p>
- * See {@link #hasDataFor(AbsoluteDate)} to determine if the cache has data
- * for {@code central} without throwing an exception.
- *
- * @param central central date
- * @param n number of neighbors
- * @return array of cached entries surrounding specified date
- * @since 12.0
- */
- protected Stream<EOPEntry> getNeighbors(final AbsoluteDate central, final int n) {
- return cache.getNeighbors(central, n);
- }
- /** Get the LoD (Length of Day) value.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the value is desired
- * @return LoD in seconds (0 if date is outside covered range)
- */
- public double getLOD(final AbsoluteDate date) {
- // check if there is data for date
- if (!this.hasDataFor(date)) {
- // no EOP data available for this date, we use a default null correction
- return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[3];
- }
- // we have EOP data for date -> interpolate correction
- double interpolated = interpolate(date, EOPEntry::getLOD);
- if (tidalCorrection != null) {
- interpolated += tidalCorrection.value(date)[3];
- }
- return interpolated;
- }
- /** Get the LoD (Length of Day) value.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the value is desired
- * @param <T> type of the filed elements
- * @return LoD in seconds (0 if date is outside covered range)
- * @since 9.0
- */
- public <T extends CalculusFieldElement<T>> T getLOD(final FieldAbsoluteDate<T> date) {
- final AbsoluteDate aDate = date.toAbsoluteDate();
- // check if there is data for date
- if (!this.hasDataFor(aDate)) {
- // no EOP data available for this date, we use a default null correction
- return (tidalCorrection == null) ? date.getField().getZero() : tidalCorrection.value(date)[3];
- }
- // we have EOP data for date -> interpolate correction
- T interpolated = interpolate(date, aDate, EOPEntry::getLOD);
- if (tidalCorrection != null) {
- interpolated = interpolated.add(tidalCorrection.value(date)[3]);
- }
- return interpolated;
- }
- /** Get the pole IERS Reference Pole correction.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the correction is desired
- * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
- * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
- */
- public PoleCorrection getPoleCorrection(final AbsoluteDate date) {
- // check if there is data for date
- if (!this.hasDataFor(date)) {
- // no EOP data available for this date, we use a default null correction
- if (tidalCorrection == null) {
- return PoleCorrection.NULL_CORRECTION;
- } else {
- final double[] correction = tidalCorrection.value(date);
- return new PoleCorrection(correction[0], correction[1]);
- }
- }
- // we have EOP data for date -> interpolate correction
- final double[] interpolated = interpolate(date,
- EOPEntry::getX, EOPEntry::getY,
- EOPEntry::getXRate, EOPEntry::getYRate);
- if (tidalCorrection != null) {
- final double[] correction = tidalCorrection.value(date);
- interpolated[0] += correction[0];
- interpolated[1] += correction[1];
- }
- return new PoleCorrection(interpolated[0], interpolated[1]);
- }
- /** Get the pole IERS Reference Pole correction.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the correction is desired
- * @param <T> type of the field elements
- * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
- * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
- */
- public <T extends CalculusFieldElement<T>> FieldPoleCorrection<T> getPoleCorrection(final FieldAbsoluteDate<T> date) {
- final AbsoluteDate aDate = date.toAbsoluteDate();
- // check if there is data for date
- if (!this.hasDataFor(aDate)) {
- // no EOP data available for this date, we use a default null correction
- if (tidalCorrection == null) {
- return new FieldPoleCorrection<>(date.getField().getZero(), date.getField().getZero());
- } else {
- final T[] correction = tidalCorrection.value(date);
- return new FieldPoleCorrection<>(correction[0], correction[1]);
- }
- }
- // we have EOP data for date -> interpolate correction
- final T[] interpolated = interpolate(date, aDate, EOPEntry::getX, EOPEntry::getY);
- if (tidalCorrection != null) {
- final T[] correction = tidalCorrection.value(date);
- interpolated[0] = interpolated[0].add(correction[0]);
- interpolated[1] = interpolated[1].add(correction[1]);
- }
- return new FieldPoleCorrection<>(interpolated[0], interpolated[1]);
- }
- /** Get the correction to the nutation parameters for equinox-based paradigm.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the correction is desired
- * @return nutation correction in longitude ΔΨ and in obliquity Δε
- * (zero if date is outside covered range)
- */
- public double[] getEquinoxNutationCorrection(final AbsoluteDate date) {
- // check if there is data for date
- if (!this.hasDataFor(date)) {
- // no EOP data available for this date, we use a default null correction
- return new double[2];
- }
- // we have EOP data for date -> interpolate correction
- return interpolate(date, EOPEntry::getDdPsi, EOPEntry::getDdEps);
- }
- /** Get the correction to the nutation parameters for equinox-based paradigm.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the correction is desired
- * @param <T> type of the field elements
- * @return nutation correction in longitude ΔΨ and in obliquity Δε
- * (zero if date is outside covered range)
- */
- public <T extends CalculusFieldElement<T>> T[] getEquinoxNutationCorrection(final FieldAbsoluteDate<T> date) {
- final AbsoluteDate aDate = date.toAbsoluteDate();
- // check if there is data for date
- if (!this.hasDataFor(aDate)) {
- // no EOP data available for this date, we use a default null correction
- return MathArrays.buildArray(date.getField(), 2);
- }
- // we have EOP data for date -> interpolate correction
- return interpolate(date, aDate, EOPEntry::getDdPsi, EOPEntry::getDdEps);
- }
- /** Get the correction to the nutation parameters for Non-Rotating Origin paradigm.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the correction is desired
- * @return nutation correction in Celestial Intermediate Pole coordinates
- * δX and δY (zero if date is outside covered range)
- */
- public double[] getNonRotatinOriginNutationCorrection(final AbsoluteDate date) {
- // check if there is data for date
- if (!this.hasDataFor(date)) {
- // no EOP data available for this date, we use a default null correction
- return new double[2];
- }
- // we have EOP data for date -> interpolate correction
- return interpolate(date, EOPEntry::getDx, EOPEntry::getDy);
- }
- /** Get the correction to the nutation parameters for Non-Rotating Origin paradigm.
- * <p>The data provided comes from the IERS files. It is smoothed data.</p>
- * @param date date at which the correction is desired
- * @param <T> type of the filed elements
- * @return nutation correction in Celestial Intermediate Pole coordinates
- * δX and δY (zero if date is outside covered range)
- */
- public <T extends CalculusFieldElement<T>> T[] getNonRotatinOriginNutationCorrection(final FieldAbsoluteDate<T> date) {
- final AbsoluteDate aDate = date.toAbsoluteDate();
- // check if there is data for date
- if (!this.hasDataFor(aDate)) {
- // no EOP data available for this date, we use a default null correction
- return MathArrays.buildArray(date.getField(), 2);
- }
- // we have EOP data for date -> interpolate correction
- return interpolate(date, aDate, EOPEntry::getDx, EOPEntry::getDy);
- }
- /** Get the ITRF version.
- * @param date date at which the value is desired
- * @return ITRF version of the EOP covering the specified date
- * @since 9.2
- */
- public ITRFVersion getITRFVersion(final AbsoluteDate date) {
- // check if there is data for date
- if (!this.hasDataFor(date)) {
- // no EOP data available for this date, we use a default ITRF 2014
- return ITRFVersion.ITRF_2014;
- }
- try {
- // we have EOP data for date
- final Optional<EOPEntry> first = getNeighbors(date, 1).findFirst();
- return first.isPresent() ? first.get().getITRFType() : ITRFVersion.ITRF_2014;
- } catch (TimeStampedCacheException tce) {
- // this should not happen because of date check performed at start
- throw new OrekitInternalError(tce);
- }
- }
- /** Check Earth orientation parameters continuity.
- * @param maxGap maximal allowed gap between entries (in seconds)
- */
- public void checkEOPContinuity(final double maxGap) {
- TimeStamped preceding = null;
- for (final TimeStamped current : this.cache.getAll()) {
- // compare the dates of preceding and current entries
- if (preceding != null && (current.getDate().durationFrom(preceding.getDate())) > maxGap) {
- throw new OrekitException(OrekitMessages.MISSING_EARTH_ORIENTATION_PARAMETERS_BETWEEN_DATES_GAP,
- preceding.getDate(), current.getDate(),
- current.getDate().durationFrom(preceding.getDate()));
- }
- // prepare next iteration
- preceding = current;
- }
- }
- /**
- * Check if the cache has data for the given date using
- * {@link #getStartDate()} and {@link #getEndDate()}.
- *
- * @param date the requested date
- * @return true if the {@link #cache} has data for the requested date, false
- * otherwise.
- */
- protected boolean hasDataFor(final AbsoluteDate date) {
- /*
- * when there is no EOP data, short circuit getStartDate, which will
- * throw an exception
- */
- return this.hasData && this.getStartDate().compareTo(date) <= 0 &&
- date.compareTo(this.getEndDate()) <= 0;
- }
- /** Get a non-modifiable view of the EOP entries.
- * @return non-modifiable view of the EOP entries
- */
- public List<EOPEntry> getEntries() {
- return cache.getAll();
- }
- /** Interpolate a single EOP component.
- * <p>
- * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
- * </p>
- * @param date interpolation date
- * @param selector selector for EOP entry component
- * @return interpolated value
- */
- private double interpolate(final AbsoluteDate date, final Function<EOPEntry, Double> selector) {
- try {
- final HermiteInterpolator interpolator = new HermiteInterpolator();
- getNeighbors(date, interpolationDegree + 1).
- forEach(entry -> interpolator.addSamplePoint(entry.getDate().durationFrom(date),
- new double[] {
- selector.apply(entry)
- }));
- return interpolator.value(0)[0];
- } catch (TimeStampedCacheException tce) {
- // this should not happen because of date check performed by caller
- throw new OrekitInternalError(tce);
- }
- }
- /** Interpolate a single EOP component.
- * <p>
- * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
- * </p>
- * @param date interpolation date
- * @param aDate interpolation date, as an {@link AbsoluteDate}
- * @param selector selector for EOP entry component
- * @param <T> type of the field elements
- * @return interpolated value
- */
- private <T extends CalculusFieldElement<T>> T interpolate(final FieldAbsoluteDate<T> date,
- final AbsoluteDate aDate,
- final Function<EOPEntry, Double> selector) {
- try {
- final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
- final T[] y = MathArrays.buildArray(date.getField(), 1);
- final T zero = date.getField().getZero();
- final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
- // for example removing derivatives
- // if T was DerivativeStructure
- getNeighbors(aDate, interpolationDegree + 1).
- forEach(entry -> {
- y[0] = zero.newInstance(selector.apply(entry));
- interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
- });
- return interpolator.value(date.durationFrom(central))[0]; // here, we introduce derivatives again (in DerivativeStructure case)
- } catch (TimeStampedCacheException tce) {
- // this should not happen because of date check performed by caller
- throw new OrekitInternalError(tce);
- }
- }
- /** Interpolate two EOP components.
- * <p>
- * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
- * </p>
- * @param date interpolation date
- * @param selector1 selector for first EOP entry component
- * @param selector2 selector for second EOP entry component
- * @return interpolated value
- */
- private double[] interpolate(final AbsoluteDate date,
- final Function<EOPEntry, Double> selector1,
- final Function<EOPEntry, Double> selector2) {
- try {
- final HermiteInterpolator interpolator = new HermiteInterpolator();
- getNeighbors(date, interpolationDegree + 1).
- forEach(entry -> interpolator.addSamplePoint(entry.getDate().durationFrom(date),
- new double[] {
- selector1.apply(entry),
- selector2.apply(entry)
- }));
- return interpolator.value(0);
- } catch (TimeStampedCacheException tce) {
- // this should not happen because of date check performed by caller
- throw new OrekitInternalError(tce);
- }
- }
- /** Interpolate two EOP components.
- * <p>
- * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
- * </p>
- * @param date interpolation date
- * @param selector1 selector for first EOP entry component
- * @param selector2 selector for second EOP entry component
- * @param selector1Rate selector for first EOP entry component rate
- * @param selector2Rate selector for second EOP entry component rate
- * @return interpolated value
- * @since 12.0
- */
- private double[] interpolate(final AbsoluteDate date,
- final Function<EOPEntry, Double> selector1,
- final Function<EOPEntry, Double> selector2,
- final Function<EOPEntry, Double> selector1Rate,
- final Function<EOPEntry, Double> selector2Rate) {
- try {
- final HermiteInterpolator interpolator = new HermiteInterpolator();
- getNeighbors(date, (interpolationDegree + 1) / 2).
- forEach(entry -> interpolator.addSamplePoint(entry.getDate().durationFrom(date),
- new double[] {
- selector1.apply(entry),
- selector2.apply(entry)
- },
- new double[] {
- selector1Rate.apply(entry),
- selector2Rate.apply(entry)
- }));
- return interpolator.value(0);
- } catch (TimeStampedCacheException tce) {
- // this should not happen because of date check performed by caller
- throw new OrekitInternalError(tce);
- }
- }
- /** Interpolate two EOP components.
- * <p>
- * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
- * </p>
- * @param date interpolation date
- * @param aDate interpolation date, as an {@link AbsoluteDate}
- * @param selector1 selector for first EOP entry component
- * @param selector2 selector for second EOP entry component
- * @param <T> type of the field elements
- * @return interpolated value
- */
- private <T extends CalculusFieldElement<T>> T[] interpolate(final FieldAbsoluteDate<T> date,
- final AbsoluteDate aDate,
- final Function<EOPEntry, Double> selector1,
- final Function<EOPEntry, Double> selector2) {
- try {
- final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
- final T[] y = MathArrays.buildArray(date.getField(), 2);
- final T zero = date.getField().getZero();
- final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
- // for example removing derivatives
- // if T was DerivativeStructure
- getNeighbors(aDate, interpolationDegree + 1).
- forEach(entry -> {
- y[0] = zero.newInstance(selector1.apply(entry));
- y[1] = zero.newInstance(selector2.apply(entry));
- interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
- });
- return interpolator.value(date.durationFrom(central)); // here, we introduce derivatives again (in DerivativeStructure case)
- } catch (TimeStampedCacheException tce) {
- // this should not happen because of date check performed by caller
- throw new OrekitInternalError(tce);
- }
- }
- /** Check if some derivatives are missing.
- * @param raw raw EOP history
- * @return true if some derivatives are missing
- * @since 12.0
- */
- private boolean missSomeDerivatives(final Collection<? extends EOPEntry> raw) {
- for (final EOPEntry entry : raw) {
- if (Double.isNaN(entry.getLOD() + entry.getXRate() + entry.getYRate())) {
- return true;
- }
- }
- return false;
- }
- /** Fix missing derivatives.
- * @param entry EOP entry to fix
- * @param rawCache raw EOP history cache
- * @return fixed entry
- * @since 12.0
- */
- private EOPEntry fixDerivatives(final EOPEntry entry, final ImmutableTimeStampedCache<EOPEntry> rawCache) {
- // helper function to differentiate some EOP parameters
- final BiFunction<EOPEntry, Function<EOPEntry, Double>, Double> differentiator =
- (e, selector) -> {
- final HermiteInterpolator interpolator = new HermiteInterpolator();
- rawCache.getNeighbors(e.getDate()).
- forEach(f -> interpolator.addSamplePoint(f.getDate().durationFrom(e.getDate()),
- new double[] {
- selector.apply(f)
- }));
- return interpolator.derivatives(0.0, 1)[1][0];
- };
- if (Double.isNaN(entry.getLOD() + entry.getXRate() + entry.getYRate())) {
- final double lod = Double.isNaN(entry.getLOD()) ?
- -differentiator.apply(entry, EOPEntry::getUT1MinusUTC) :
- entry.getLOD();
- final double xRate = Double.isNaN(entry.getXRate()) ?
- differentiator.apply(entry, EOPEntry::getX) :
- entry.getXRate();
- final double yRate = Double.isNaN(entry.getYRate()) ?
- differentiator.apply(entry, EOPEntry::getY) :
- entry.getYRate();
- return new EOPEntry(entry.getMjd(),
- entry.getUT1MinusUTC(), lod,
- entry.getX(), entry.getY(), xRate, yRate,
- entry.getDdPsi(), entry.getDdEps(),
- entry.getDx(), entry.getDy(),
- entry.getITRFType(), entry.getDate());
- } else {
- // the entry already has all derivatives
- return entry;
- }
- }
- /** Internal class for caching tidal correction. */
- private static class TidalCorrectionEntry implements TimeStamped {
- /** Entry date. */
- private final AbsoluteDate date;
- /** Correction. */
- private final double[] correction;
- /** Simple constructor.
- * @param date entry date
- * @param correction correction on the EOP parameters (xp, yp, ut1, lod)
- */
- TidalCorrectionEntry(final AbsoluteDate date, final double[] correction) {
- this.date = date;
- this.correction = correction;
- }
- /** {@inheritDoc} */
- @Override
- public AbsoluteDate getDate() {
- return date;
- }
- }
- /** Local generator for thread-safe cache. */
- private static class CachedCorrection
- implements TimeVectorFunction, TimeStampedGenerator<TidalCorrectionEntry> {
- /** Correction to apply to EOP (may be null). */
- private final TimeVectorFunction tidalCorrection;
- /** Step between generated entries. */
- private final double step;
- /** Tidal corrections entries cache. */
- private final TimeStampedCache<TidalCorrectionEntry> cache;
- /** Simple constructor.
- * @param tidalCorrection function computing the tidal correction
- */
- CachedCorrection(final TimeVectorFunction tidalCorrection) {
- this.step = 60 * 60;
- this.tidalCorrection = tidalCorrection;
- this.cache =
- new GenericTimeStampedCache<>(8,
- OrekitConfiguration.getCacheSlotsNumber(),
- Constants.JULIAN_DAY * 30,
- Constants.JULIAN_DAY,
- this);
- }
- /** {@inheritDoc} */
- @Override
- public double[] value(final AbsoluteDate date) {
- try {
- // set up an interpolator
- final HermiteInterpolator interpolator = new HermiteInterpolator();
- cache.getNeighbors(date).forEach(entry -> interpolator.addSamplePoint(entry.date.durationFrom(date), entry.correction));
- // interpolate to specified date
- return interpolator.value(0.0);
- } catch (TimeStampedCacheException tsce) {
- // this should never happen
- throw new OrekitInternalError(tsce);
- }
- }
- /** {@inheritDoc} */
- @Override
- public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
- try {
- final AbsoluteDate aDate = date.toAbsoluteDate();
- final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
- final T[] y = MathArrays.buildArray(date.getField(), 4);
- final T zero = date.getField().getZero();
- final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
- // for example removing derivatives
- // if T was DerivativeStructure
- cache.getNeighbors(aDate).forEach(entry -> {
- for (int i = 0; i < y.length; ++i) {
- y[i] = zero.newInstance(entry.correction[i]);
- }
- interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
- });
- // interpolate to specified date
- return interpolator.value(date.durationFrom(central)); // here, we introduce derivatives again (in DerivativeStructure case)
- } catch (TimeStampedCacheException tsce) {
- // this should never happen
- throw new OrekitInternalError(tsce);
- }
- }
- /** {@inheritDoc} */
- @Override
- public List<TidalCorrectionEntry> generate(final AbsoluteDate existingDate, final AbsoluteDate date) {
- final List<TidalCorrectionEntry> generated = new ArrayList<>();
- if (existingDate == null) {
- // no prior existing entries, just generate a first set
- for (int i = -cache.getMaxNeighborsSize() / 2; generated.size() < cache.getMaxNeighborsSize(); ++i) {
- final AbsoluteDate t = date.shiftedBy(i * step);
- generated.add(new TidalCorrectionEntry(t, tidalCorrection.value(t)));
- }
- } else {
- // some entries have already been generated
- // add the missing ones up to specified date
- AbsoluteDate t = existingDate;
- if (date.compareTo(t) > 0) {
- // forward generation
- do {
- t = t.shiftedBy(step);
- generated.add(new TidalCorrectionEntry(t, tidalCorrection.value(t)));
- } while (t.compareTo(date) <= 0);
- } else {
- // backward generation
- do {
- t = t.shiftedBy(-step);
- generated.add(0, new TidalCorrectionEntry(t, tidalCorrection.value(t)));
- } while (t.compareTo(date) >= 0);
- }
- }
- // return the generated transforms
- return generated;
- }
- }
- }