EOPHistory.java
/* Copyright 2002-2016 CS Systèmes d'Information
* Licensed to CS Systèmes d'Information (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.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.math3.analysis.interpolation.HermiteInterpolator;
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.TimeFunction;
import org.orekit.time.TimeStamped;
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
*/
public class EOPHistory implements Serializable {
/** Serializable UID. */
private static final long serialVersionUID = 20131010L;
/** Number of points to use in interpolation. */
private static final int INTERPOLATION_POINTS = 4;
/**
* 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 TimeFunction<double[]> tidalCorrection;
/** Simple constructor.
* @param conventions IERS conventions to which EOP refers
* @param data the EOP data to use
* @param simpleEOP if true, tidal effects are ignored when interpolating EOP
* @exception OrekitException if tidal correction model cannot be loaded
*/
protected EOPHistory(final IERSConventions conventions,
final Collection<EOPEntry> data,
final boolean simpleEOP)
throws OrekitException {
this(conventions, data, simpleEOP ? null : new CachedCorrection(conventions.getEOPTidalCorrection()));
}
/** Simple constructor.
* @param conventions IERS conventions to which EOP refers
* @param data the EOP data to use
* @param tidalCorrection correction to apply to EOP
* @exception OrekitException if tidal correction model cannot be loaded
*/
private EOPHistory(final IERSConventions conventions,
final Collection<EOPEntry> data,
final TimeFunction<double[]> tidalCorrection)
throws OrekitException {
this.conventions = conventions;
this.tidalCorrection = tidalCorrection;
if (data.size() >= INTERPOLATION_POINTS) {
// enough data to interpolate
cache = new ImmutableTimeStampedCache<EOPEntry>(INTERPOLATION_POINTS, data);
hasData = true;
} else {
// not enough data to interpolate -> always use null correction
cache = ImmutableTimeStampedCache.emptyCache();
hasData = false;
}
}
/** Get non-interpolating version of the instance.
* @return non-interpolatig version of the instance
* @exception OrekitException if tidal correction model cannot be loaded
*/
public EOPHistory getNonInterpolatingEOPHistory()
throws OrekitException {
return new EOPHistory(conventions, getEntries(), conventions.getEOPTidalCorrection());
}
/** Check if the instance uses interpolation on tidal corrections.
* @return true if the instance uses interpolation on tidal corrections
*/
public boolean usesInterpolation() {
return tidalCorrection != null && 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 List<EOPEntry> neighbors = getNeighbors(date);
final HermiteInterpolator interpolator = new HermiteInterpolator();
final double firstDUT = neighbors.get(0).getUT1MinusUTC();
boolean beforeLeap = true;
for (final EOPEntry neighbor : neighbors) {
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(date) <= 0) {
beforeLeap = false;
}
} else {
dut = neighbor.getUT1MinusUTC();
}
interpolator.addSamplePoint(neighbor.getDate().durationFrom(date),
new double[] {
dut
});
}
double interpolated = interpolator.value(0)[0];
if (tidalCorrection != null) {
interpolated += tidalCorrection.value(date)[2];
}
return beforeLeap ? interpolated : interpolated + 1.0;
} catch (TimeStampedCacheException tce) {
//this should not happen because of date check above
throw new OrekitInternalError(tce);
}
}
/**
* 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
* @return array of cached entries surrounding specified date
* @exception TimeStampedCacheException if EOP data cannot be retrieved
*/
protected List<EOPEntry> getNeighbors(final AbsoluteDate central) throws TimeStampedCacheException {
return cache.getNeighbors(central);
}
/** 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
try {
final HermiteInterpolator interpolator = new HermiteInterpolator();
for (final EOPEntry entry : getNeighbors(date)) {
interpolator.addSamplePoint(entry.getDate().durationFrom(date),
new double[] {
entry.getLOD()
});
}
double interpolated = interpolator.value(0)[0];
if (tidalCorrection != null) {
interpolated += tidalCorrection.value(date)[3];
}
return interpolated;
} catch (TimeStampedCacheException tce) {
// this should not happen because of date check above
throw new OrekitInternalError(tce);
}
}
/** 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
try {
final HermiteInterpolator interpolator = new HermiteInterpolator();
for (final EOPEntry entry : getNeighbors(date)) {
interpolator.addSamplePoint(entry.getDate().durationFrom(date),
new double[] {
entry.getX(), entry.getY()
});
}
final double[] interpolated = interpolator.value(0);
if (tidalCorrection != null) {
final double[] correction = tidalCorrection.value(date);
interpolated[0] += correction[0];
interpolated[1] += correction[1];
}
return new PoleCorrection(interpolated[0], interpolated[1]);
} catch (TimeStampedCacheException tce) {
// this should not happen because of date check above
throw new OrekitInternalError(tce);
}
}
/** 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
try {
final HermiteInterpolator interpolator = new HermiteInterpolator();
for (final EOPEntry entry : getNeighbors(date)) {
interpolator.addSamplePoint(entry.getDate().durationFrom(date),
new double[] {
entry.getDdPsi(), entry.getDdEps()
});
}
return interpolator.value(0);
} catch (TimeStampedCacheException tce) {
// this should not happen because of date check above
throw new OrekitInternalError(tce);
}
}
/** 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 Intermediat 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
try {
final HermiteInterpolator interpolator = new HermiteInterpolator();
for (final EOPEntry entry : getNeighbors(date)) {
interpolator.addSamplePoint(entry.getDate().durationFrom(date),
new double[] {
entry.getDx(), entry.getDy()
});
}
return interpolator.value(0);
} catch (TimeStampedCacheException tce) {
// this should not happen because of date check above
throw new OrekitInternalError(tce);
}
}
/** Check Earth orientation parameters continuity.
* @param maxGap maximal allowed gap between entries (in seconds)
* @exception OrekitException if there are holes in the data sequence
*/
public void checkEOPContinuity(final double maxGap) throws OrekitException {
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,
preceding.getDate(), current.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
*/
List<EOPEntry> getEntries() {
return cache.getAll();
}
/** Replace the instance with a data transfer object for serialization.
* <p>
* This intermediate class serializes only the frame key.
* </p>
* @return data transfer object that will be serialized
*/
private Object writeReplace() {
return new DataTransferObject(conventions, getEntries(), tidalCorrection == null);
}
/** Internal class used only for serialization. */
private static class DataTransferObject implements Serializable {
/** Serializable UID. */
private static final long serialVersionUID = 20131010L;
/** IERS conventions. */
private final IERSConventions conventions;
/** EOP entries. */
private final List<EOPEntry> entries;
/** Indicator for simple interpolation without tidal effects. */
private final boolean simpleEOP;
/** Simple constructor.
* @param conventions IERS conventions to which EOP refers
* @param entries the EOP data to use
* @param simpleEOP if true, tidal effects are ignored when interpolating EOP
*/
DataTransferObject(final IERSConventions conventions,
final List<EOPEntry> entries,
final boolean simpleEOP) {
this.conventions = conventions;
this.entries = entries;
this.simpleEOP = simpleEOP;
}
/** Replace the deserialized data transfer object with a {@link EOPHistory}.
* @return replacement {@link EOPHistory}
*/
private Object readResolve() {
try {
// retrieve a managed frame
return new EOPHistory(conventions, entries, simpleEOP);
} catch (OrekitException oe) {
throw new OrekitInternalError(oe);
}
}
}
/** 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 TimeFunction<double[]>, TimeStampedGenerator<TidalCorrectionEntry> {
/** Correction to apply to EOP (may be null). */
private final TimeFunction<double[]> 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 TimeFunction<double[]> tidalCorrection) {
this.step = 60 * 60;
this.tidalCorrection = tidalCorrection;
this.cache =
new GenericTimeStampedCache<TidalCorrectionEntry>(8,
OrekitConfiguration.getCacheSlotsNumber(),
Constants.JULIAN_DAY * 30,
Constants.JULIAN_DAY,
this,
TidalCorrectionEntry.class);
}
/** {@inheritDoc} */
@Override
public double[] value(final AbsoluteDate date) {
try {
// set up an interpolator
final HermiteInterpolator interpolator = new HermiteInterpolator();
for (final TidalCorrectionEntry entry : cache.getNeighbors(date)) {
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 List<TidalCorrectionEntry> generate(final TidalCorrectionEntry existing, final AbsoluteDate date) {
final List<TidalCorrectionEntry> generated = new ArrayList<TidalCorrectionEntry>();
if (existing == null) {
// no prior existing entries, just generate a first set
for (int i = -cache.getNeighborsSize() / 2; generated.size() < cache.getNeighborsSize(); ++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 = existing.getDate();
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;
}
}
}