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
19 import java.io.Serializable;
20 import java.util.Collection;
21 import java.util.List;
22
23 import org.apache.commons.math3.analysis.interpolation.HermiteInterpolator;
24 import org.orekit.errors.OrekitException;
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.IERSConventions;
31 import org.orekit.utils.ImmutableTimeStampedCache;
32
33 /** This class loads any kind of Earth Orientation Parameter data throughout a large time range.
34 * @author Pascal Parraud
35 */
36 public class EOPHistory implements Serializable {
37
38 /** Serializable UID. */
39 private static final long serialVersionUID = 20131010L;
40
41 /** Number of points to use in interpolation. */
42 private static final int INTERPOLATION_POINTS = 4;
43
44 /**
45 * If this history has any EOP data.
46 *
47 * @see #hasDataFor(AbsoluteDate)
48 */
49 private final boolean hasData;
50
51 /** EOP history entries. */
52 private final transient ImmutableTimeStampedCache<EOPEntry> cache;
53
54 /** IERS conventions to which EOP refers. */
55 private final IERSConventions conventions;
56
57 /** Correction to apply to EOP (may be null). */
58 private final transient TimeFunction<double[]> tidalCorrection;
59
60 /** Simple constructor.
61 * @param conventions IERS conventions to which EOP refers
62 * @param data the EOP data to use
63 * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
64 * @exception OrekitException if tidal correction model cannot be loaded
65 */
66 protected EOPHistory(final IERSConventions conventions,
67 final Collection<EOPEntry> data,
68 final boolean simpleEOP)
69 throws OrekitException {
70 this.conventions = conventions;
71 tidalCorrection = simpleEOP ? null : conventions.getEOPTidalCorrection();
72 if (data.size() >= INTERPOLATION_POINTS) {
73 // enough data to interpolate
74 cache = new ImmutableTimeStampedCache<EOPEntry>(INTERPOLATION_POINTS, data);
75 hasData = true;
76 } else {
77 // not enough data to interpolate -> always use null correction
78 cache = ImmutableTimeStampedCache.emptyCache();
79 hasData = false;
80 }
81 }
82
83 /** Get the IERS conventions to which these EOP apply.
84 * @return IERS conventions to which these EOP apply
85 */
86 public IERSConventions getConventions() {
87 return conventions;
88 }
89
90 /** Get the date of the first available Earth Orientation Parameters.
91 * @return the start date of the available data
92 */
93 public AbsoluteDate getStartDate() {
94 return this.cache.getEarliest().getDate();
95 }
96
97 /** Get the date of the last available Earth Orientation Parameters.
98 * @return the end date of the available data
99 */
100 public AbsoluteDate getEndDate() {
101 return this.cache.getLatest().getDate();
102 }
103
104 /** Get the UT1-UTC value.
105 * <p>The data provided comes from the IERS files. It is smoothed data.</p>
106 * @param date date at which the value is desired
107 * @return UT1-UTC in seconds (0 if date is outside covered range)
108 */
109 public double getUT1MinusUTC(final AbsoluteDate date) {
110 //check if there is data for date
111 if (!this.hasDataFor(date)) {
112 // no EOP data available for this date, we use a default 0.0 offset
113 return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[2];
114 }
115 //we have EOP data -> interpolate offset
116 try {
117 final List<EOPEntry> neighbors = getNeighbors(date);
118 final HermiteInterpolator interpolator = new HermiteInterpolator();
119 final double firstDUT = neighbors.get(0).getUT1MinusUTC();
120 boolean beforeLeap = true;
121 for (final EOPEntry neighbor : neighbors) {
122 final double dut;
123 if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
124 // there was a leap second between the entries
125 dut = neighbor.getUT1MinusUTC() - 1.0;
126 if (neighbor.getDate().compareTo(date) <= 0) {
127 beforeLeap = false;
128 }
129 } else {
130 dut = neighbor.getUT1MinusUTC();
131 }
132 interpolator.addSamplePoint(neighbor.getDate().durationFrom(date),
133 new double[] {
134 dut
135 });
136 }
137 double interpolated = interpolator.value(0)[0];
138 if (tidalCorrection != null) {
139 interpolated += tidalCorrection.value(date)[2];
140 }
141 return beforeLeap ? interpolated : interpolated + 1.0;
142 } catch (TimeStampedCacheException tce) {
143 //this should not happen because of date check above
144 throw OrekitException.createInternalError(tce);
145 }
146 }
147
148 /**
149 * Get the entries surrounding a central date.
150 * <p>
151 * See {@link #hasDataFor(AbsoluteDate)} to determine if the cache has data
152 * for {@code central} without throwing an exception.
153 *
154 * @param central central date
155 * @return array of cached entries surrounding specified date
156 * @exception TimeStampedCacheException if EOP data cannot be retrieved
157 */
158 protected List<EOPEntry> getNeighbors(final AbsoluteDate central) throws TimeStampedCacheException {
159 return cache.getNeighbors(central);
160 }
161
162 /** Get the LoD (Length of Day) value.
163 * <p>The data provided comes from the IERS files. It is smoothed data.</p>
164 * @param date date at which the value is desired
165 * @return LoD in seconds (0 if date is outside covered range)
166 */
167 public double getLOD(final AbsoluteDate date) {
168 //check if there is data for date
169 if (!this.hasDataFor(date)) {
170 // no EOP data available for this date, we use a default null correction
171 return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[3];
172 }
173 //we have EOP data for date -> interpolate correction
174 try {
175 final HermiteInterpolator interpolator = new HermiteInterpolator();
176 for (final EOPEntry entry : getNeighbors(date)) {
177 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
178 new double[] {
179 entry.getLOD()
180 });
181 }
182 double interpolated = interpolator.value(0)[0];
183 if (tidalCorrection != null) {
184 interpolated += tidalCorrection.value(date)[3];
185 }
186 return interpolated;
187 } catch (TimeStampedCacheException tce) {
188 // this should not happen because of date check above
189 throw OrekitException.createInternalError(tce);
190 }
191 }
192
193 /** Get the pole IERS Reference Pole correction.
194 * <p>The data provided comes from the IERS files. It is smoothed data.</p>
195 * @param date date at which the correction is desired
196 * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
197 * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
198 */
199 public PoleCorrection getPoleCorrection(final AbsoluteDate date) {
200 // check if there is data for date
201 if (!this.hasDataFor(date)) {
202 // no EOP data available for this date, we use a default null correction
203 if (tidalCorrection == null) {
204 return PoleCorrection.NULL_CORRECTION;
205 } else {
206 final double[] correction = tidalCorrection.value(date);
207 return new PoleCorrection(correction[0], correction[1]);
208 }
209 }
210 //we have EOP data for date -> interpolate correction
211 try {
212 final HermiteInterpolator interpolator = new HermiteInterpolator();
213 for (final EOPEntry entry : getNeighbors(date)) {
214 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
215 new double[] {
216 entry.getX(), entry.getY()
217 });
218 }
219 final double[] interpolated = interpolator.value(0);
220 if (tidalCorrection != null) {
221 final double[] correction = tidalCorrection.value(date);
222 interpolated[0] += correction[0];
223 interpolated[1] += correction[1];
224 }
225 return new PoleCorrection(interpolated[0], interpolated[1]);
226 } catch (TimeStampedCacheException tce) {
227 // this should not happen because of date check above
228 throw OrekitException.createInternalError(tce);
229 }
230 }
231
232 /** Get the correction to the nutation parameters for equinox-based paradigm.
233 * <p>The data provided comes from the IERS files. It is smoothed data.</p>
234 * @param date date at which the correction is desired
235 * @return nutation correction in longitude ΔΨ and in obliquity Δε
236 * (zero if date is outside covered range)
237 */
238 public double[] getEquinoxNutationCorrection(final AbsoluteDate date) {
239 // check if there is data for date
240 if (!this.hasDataFor(date)) {
241 // no EOP data available for this date, we use a default null correction
242 return new double[2];
243 }
244 //we have EOP data for date -> interpolate correction
245 try {
246 final HermiteInterpolator interpolator = new HermiteInterpolator();
247 for (final EOPEntry entry : getNeighbors(date)) {
248 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
249 new double[] {
250 entry.getDdPsi(), entry.getDdEps()
251 });
252 }
253 return interpolator.value(0);
254 } catch (TimeStampedCacheException tce) {
255 // this should not happen because of date check above
256 throw OrekitException.createInternalError(tce);
257 }
258 }
259
260 /** Get the correction to the nutation parameters for Non-Rotating Origin paradigm.
261 * <p>The data provided comes from the IERS files. It is smoothed data.</p>
262 * @param date date at which the correction is desired
263 * @return nutation correction in Celestial Intermediat Pole coordinates
264 * δX and δY (zero if date is outside covered range)
265 */
266 public double[] getNonRotatinOriginNutationCorrection(final AbsoluteDate date) {
267 // check if there is data for date
268 if (!this.hasDataFor(date)) {
269 // no EOP data available for this date, we use a default null correction
270 return new double[2];
271 }
272 //we have EOP data for date -> interpolate correction
273 try {
274 final HermiteInterpolator interpolator = new HermiteInterpolator();
275 for (final EOPEntry entry : getNeighbors(date)) {
276 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
277 new double[] {
278 entry.getDx(), entry.getDy()
279 });
280 }
281 return interpolator.value(0);
282 } catch (TimeStampedCacheException tce) {
283 // this should not happen because of date check above
284 throw OrekitException.createInternalError(tce);
285 }
286 }
287
288 /** Check Earth orientation parameters continuity.
289 * @param maxGap maximal allowed gap between entries (in seconds)
290 * @exception OrekitException if there are holes in the data sequence
291 */
292 public void checkEOPContinuity(final double maxGap) throws OrekitException {
293 TimeStamped preceding = null;
294 for (final TimeStamped current : this.cache.getAll()) {
295
296 // compare the dates of preceding and current entries
297 if ((preceding != null) && ((current.getDate().durationFrom(preceding.getDate())) > maxGap)) {
298 throw new OrekitException(OrekitMessages.MISSING_EARTH_ORIENTATION_PARAMETERS_BETWEEN_DATES,
299 preceding.getDate(), current.getDate());
300 }
301
302 // prepare next iteration
303 preceding = current;
304
305 }
306 }
307
308 /**
309 * Check if the cache has data for the given date using
310 * {@link #getStartDate()} and {@link #getEndDate()}.
311 *
312 * @param date the requested date
313 * @return true if the {@link #cache} has data for the requested date, false
314 * otherwise.
315 */
316 protected boolean hasDataFor(final AbsoluteDate date) {
317 /*
318 * when there is no EOP data, short circuit getStartDate, which will
319 * throw an exception
320 */
321 return this.hasData && this.getStartDate().compareTo(date) <= 0 &&
322 date.compareTo(this.getEndDate()) <= 0;
323 }
324
325 /** Get a non-modifiable view of the EOP entries.
326 * @return non-modifiable view of the EOP entries
327 */
328 List<EOPEntry> getEntries() {
329 return cache.getAll();
330 }
331
332 /** Replace the instance with a data transfer object for serialization.
333 * <p>
334 * This intermediate class serializes only the frame key.
335 * </p>
336 * @return data transfer object that will be serialized
337 */
338 private Object writeReplace() {
339 return new DataTransferObject(conventions, getEntries(), tidalCorrection == null);
340 }
341
342 /** Internal class used only for serialization. */
343 private static class DataTransferObject implements Serializable {
344
345 /** Serializable UID. */
346 private static final long serialVersionUID = 20131010L;
347
348 /** IERS conventions. */
349 private final IERSConventions conventions;
350
351 /** EOP entries. */
352 private final List<EOPEntry> entries;
353
354 /** Indicator for simple interpolation without tidal effects. */
355 private final boolean simpleEOP;
356
357 /** Simple constructor.
358 * @param conventions IERS conventions to which EOP refers
359 * @param entries the EOP data to use
360 * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
361 */
362 public DataTransferObject(final IERSConventions conventions,
363 final List<EOPEntry> entries,
364 final boolean simpleEOP) {
365 this.conventions = conventions;
366 this.entries = entries;
367 this.simpleEOP = simpleEOP;
368 }
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 OrekitException.createInternalError(oe);
379 }
380 }
381
382 }
383
384 }