Java tutorial
/* Copyright 2002-2015 CS Systmes d'Information * Licensed to CS Systmes 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; } } }