1 /* Copyright 2002-2021 CS GROUP
2 * Licensed to CS GROUP (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.propagation;
18
19
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.stream.Stream;
26
27 import org.hipparchus.Field;
28 import org.hipparchus.CalculusFieldElement;
29 import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
30 import org.hipparchus.exception.LocalizedCoreFormats;
31 import org.hipparchus.exception.MathIllegalArgumentException;
32 import org.hipparchus.exception.MathIllegalStateException;
33 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
34 import org.hipparchus.util.FastMath;
35 import org.hipparchus.util.MathArrays;
36 import org.orekit.attitudes.FieldAttitude;
37 import org.orekit.attitudes.LofOffset;
38 import org.orekit.errors.OrekitException;
39 import org.orekit.errors.OrekitIllegalArgumentException;
40 import org.orekit.errors.OrekitIllegalStateException;
41 import org.orekit.errors.OrekitMessages;
42 import org.orekit.frames.FieldTransform;
43 import org.orekit.frames.Frame;
44 import org.orekit.frames.LOFType;
45 import org.orekit.orbits.FieldOrbit;
46 import org.orekit.orbits.PositionAngle;
47 import org.orekit.time.FieldAbsoluteDate;
48 import org.orekit.time.FieldTimeInterpolable;
49 import org.orekit.time.FieldTimeShiftable;
50 import org.orekit.time.FieldTimeStamped;
51 import org.orekit.utils.FieldAbsolutePVCoordinates;
52 import org.orekit.utils.FieldPVCoordinates;
53 import org.orekit.utils.TimeStampedFieldPVCoordinates;
54
55
56 /** This class is the representation of a complete state holding orbit, attitude
57 * and mass information at a given date.
58 *
59 * <p>It contains an {@link FieldOrbit orbital state} at a current
60 * {@link FieldAbsoluteDate} both handled by an {@link FieldOrbit}, plus the current
61 * mass and attitude. FieldOrbitand state are guaranteed to be consistent in terms
62 * of date and reference frame. The spacecraft state may also contain additional
63 * states, which are simply named double arrays which can hold any user-defined
64 * data.
65 * </p>
66 * <p>
67 * The state can be slightly shifted to close dates. This shift is based on
68 * a simple Keplerian model for orbit, a linear extrapolation for attitude
69 * taking the spin rate into account and no mass change. It is <em>not</em>
70 * intended as a replacement for proper orbit and attitude propagation but
71 * should be sufficient for either small time shifts or coarse accuracy.
72 * </p>
73 * <p>
74 * The instance {@code FieldSpacecraftState} is guaranteed to be immutable.
75 * </p>
76 * @see org.orekit.propagation.numerical.NumericalPropagator
77 * @author Fabien Maussion
78 * @author Véronique Pommier-Maurussane
79 * @author Luc Maisonobe
80 * @author Vincent Mouraux
81 */
82 public class FieldSpacecraftState <T extends CalculusFieldElement<T>>
83 implements FieldTimeStamped<T>, FieldTimeShiftable<FieldSpacecraftState<T>, T>, FieldTimeInterpolable<FieldSpacecraftState<T>, T> {
84
85 /** Default mass. */
86 private static final double DEFAULT_MASS = 1000.0;
87
88 /**
89 * tolerance on date comparison in {@link #checkConsistency(FieldOrbit<T>, FieldAttitude<T>)}. 100 ns
90 * corresponds to sub-mm accuracy at LEO orbital velocities.
91 */
92 private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;
93
94 /** Orbital state. */
95 private final FieldOrbit<T> orbit;
96
97 /** Trajectory state, when it is not an orbit. */
98 private final FieldAbsolutePVCoordinates<T> absPva;
99
100 /** FieldAttitude<T>. */
101 private final FieldAttitude<T> attitude;
102
103 /** Current mass (kg). */
104 private final T mass;
105
106 /** Additional states. */
107 private final Map<String, T[]> additional;
108
109 /** Build a spacecraft state from orbit only.
110 * <p>FieldAttitude and mass are set to unspecified non-null arbitrary values.</p>
111 * @param orbit the orbit
112 */
113 public FieldSpacecraftState(final FieldOrbit<T> orbit) {
114 this(orbit,
115 new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
116 orbit.getA().getField().getZero().add(DEFAULT_MASS), null);
117 }
118
119 /** Build a spacecraft state from orbit and attitude provider.
120 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
121 * @param orbit the orbit
122 * @param attitude attitude
123 * @exception IllegalArgumentException if orbit and attitude dates
124 * or frames are not equal
125 */
126 public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude)
127 throws IllegalArgumentException {
128 this(orbit, attitude, orbit.getA().getField().getZero().add(DEFAULT_MASS), null);
129 }
130
131 /** Create a new instance from orbit and mass.
132 * <p>FieldAttitude law is set to an unspecified default attitude.</p>
133 * @param orbit the orbit
134 * @param mass the mass (kg)
135 */
136 public FieldSpacecraftState(final FieldOrbit<T> orbit, final T mass) {
137 this(orbit,
138 new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
139 mass, null);
140 }
141
142 /** Build a spacecraft state from orbit, attitude provider and mass.
143 * @param orbit the orbit
144 * @param attitude attitude
145 * @param mass the mass (kg)
146 * @exception IllegalArgumentException if orbit and attitude dates
147 * or frames are not equal
148 */
149 public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final T mass)
150 throws IllegalArgumentException {
151 this(orbit, attitude, mass, null);
152 }
153
154 /** Build a spacecraft state from orbit only.
155 * <p>FieldAttitude and mass are set to unspecified non-null arbitrary values.</p>
156 * @param orbit the orbit
157 * @param additional additional states
158 */
159 public FieldSpacecraftState(final FieldOrbit<T> orbit, final Map<String, T[]> additional) {
160 this(orbit,
161 new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
162 orbit.getA().getField().getZero().add(DEFAULT_MASS), additional);
163 }
164
165 /** Build a spacecraft state from orbit and attitude provider.
166 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
167 * @param orbit the orbit
168 * @param attitude attitude
169 * @param additional additional states
170 * @exception IllegalArgumentException if orbit and attitude dates
171 * or frames are not equal
172 */
173 public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final Map<String, T[]> additional)
174 throws IllegalArgumentException {
175 this(orbit, attitude, orbit.getA().getField().getZero().add(DEFAULT_MASS), additional);
176 }
177
178 /** Create a new instance from orbit and mass.
179 * <p>FieldAttitude law is set to an unspecified default attitude.</p>
180 * @param orbit the orbit
181 * @param mass the mass (kg)
182 * @param additional additional states
183 */
184 public FieldSpacecraftState(final FieldOrbit<T> orbit, final T mass, final Map<String, T[]> additional) {
185 this(orbit,
186 new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
187 mass, additional);
188 }
189
190 /** Build a spacecraft state from orbit, attitude provider and mass.
191 * @param orbit the orbit
192 * @param attitude attitude
193 * @param mass the mass (kg)
194 * @param additional additional states (may be null if no additional states are available)
195 * @exception IllegalArgumentException if orbit and attitude dates
196 * or frames are not equal
197 */
198 public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude,
199 final T mass, final Map<String, T[]> additional)
200 throws IllegalArgumentException {
201 checkConsistency(orbit, attitude);
202 this.orbit = orbit;
203 this.attitude = attitude;
204 this.mass = mass;
205 this.absPva = null;
206
207 if (additional == null) {
208 this.additional = Collections.emptyMap();
209 } else {
210
211 this.additional = new HashMap<String, T[]>(additional.size());
212 for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
213 this.additional.put(entry.getKey(), entry.getValue().clone());
214 }
215 }
216 }
217
218 /** Convert a {@link FieldSpacecraftState}.
219 * @param field field to which the elements belong
220 * @param state state to convert
221 */
222 public FieldSpacecraftState(final Field<T> field, final SpacecraftState state) {
223
224 if (state.isOrbitDefined()) {
225 final double[] stateD = new double[6];
226 final double[] stateDotD = state.getOrbit().hasDerivatives() ? new double[6] : null;
227 state.getOrbit().getType().mapOrbitToArray(state.getOrbit(), PositionAngle.TRUE, stateD, stateDotD);
228 final T[] stateF = MathArrays.buildArray(field, 6);
229 for (int i = 0; i < stateD.length; ++i) {
230 stateF[i] = field.getZero().add(stateD[i]);
231 }
232 final T[] stateDotF;
233 if (stateDotD == null) {
234 stateDotF = null;
235 } else {
236 stateDotF = MathArrays.buildArray(field, 6);
237 for (int i = 0; i < stateDotD.length; ++i) {
238 stateDotF[i] = field.getZero().add(stateDotD[i]);
239 }
240 }
241
242 final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, state.getDate());
243
244 this.orbit = state.getOrbit().getType().mapArrayToOrbit(stateF, stateDotF,
245 PositionAngle.TRUE,
246 dateF, field.getZero().add(state.getMu()), state.getFrame());
247 this.attitude = new FieldAttitude<>(field, state.getAttitude());
248 this.mass = field.getZero().add(state.getMass());
249 this.absPva = null;
250 final Map<String, double[]> additionalD = state.getAdditionalStates();
251 if (additionalD.isEmpty()) {
252 this.additional = Collections.emptyMap();
253 } else {
254 this.additional = new HashMap<String, T[]>(additionalD.size());
255 for (final Map.Entry<String, double[]> entry : additionalD.entrySet()) {
256 final T[] array = MathArrays.buildArray(field, entry.getValue().length);
257 for (int k = 0; k < array.length; ++k) {
258 array[k] = field.getZero().add(entry.getValue()[k]);
259 }
260 this.additional.put(entry.getKey(), array);
261 }
262 }
263
264 } else {
265 final T zero = field.getZero();
266 final T x = zero.add(state.getPVCoordinates().getPosition().getX());
267 final T y = zero.add(state.getPVCoordinates().getPosition().getY());
268 final T z = zero.add(state.getPVCoordinates().getPosition().getZ());
269 final T vx = zero.add(state.getPVCoordinates().getVelocity().getX());
270 final T vy = zero.add(state.getPVCoordinates().getVelocity().getY());
271 final T vz = zero.add(state.getPVCoordinates().getVelocity().getZ());
272 final T ax = zero.add(state.getPVCoordinates().getAcceleration().getX());
273 final T ay = zero.add(state.getPVCoordinates().getAcceleration().getY());
274 final T az = zero.add(state.getPVCoordinates().getAcceleration().getZ());
275 final FieldPVCoordinates<T> pva_f = new FieldPVCoordinates<>(new FieldVector3D<>(x, y, z), new FieldVector3D<>(vx, vy, vz), new FieldVector3D<>(ax, ay, az));
276 final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, state.getDate());
277 this.orbit = null;
278 this.attitude = new FieldAttitude<>(field, state.getAttitude());
279 this.mass = field.getZero().add(state.getMass());
280 this.absPva = new FieldAbsolutePVCoordinates<>(state.getFrame(), dateF, pva_f);
281 final Map<String, double[]> additionalD = state.getAdditionalStates();
282 if (additionalD.isEmpty()) {
283 this.additional = Collections.emptyMap();
284 } else {
285 this.additional = new HashMap<String, T[]>(additionalD.size());
286 for (final Map.Entry<String, double[]> entry : additionalD.entrySet()) {
287 final T[] array = MathArrays.buildArray(field, entry.getValue().length);
288 for (int k = 0; k < array.length; ++k) {
289 array[k] = field.getZero().add(entry.getValue()[k]);
290 }
291 this.additional.put(entry.getKey(), array);
292 }
293 }
294 }
295 }
296
297 /** Build a spacecraft state from orbit only.
298 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
299 * @param absPva position-velocity-acceleration
300 */
301 public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva) {
302 this(absPva,
303 new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
304 absPva.getDate().getField().getZero().add(DEFAULT_MASS), null);
305 }
306
307 /** Build a spacecraft state from orbit and attitude provider.
308 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
309 * @param absPva position-velocity-acceleration
310 * @param attitude attitude
311 * @exception IllegalArgumentException if orbit and attitude dates
312 * or frames are not equal
313 */
314 public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
315 throws IllegalArgumentException {
316 this(absPva, attitude, absPva.getDate().getField().getZero().add(DEFAULT_MASS), null);
317 }
318
319 /** Create a new instance from orbit and mass.
320 * <p>Attitude law is set to an unspecified default attitude.</p>
321 * @param absPva position-velocity-acceleration
322 * @param mass the mass (kg)
323 */
324 public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final T mass) {
325 this(absPva,
326 new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
327 mass, null);
328 }
329
330 /** Build a spacecraft state from orbit, attitude provider and mass.
331 * @param absPva position-velocity-acceleration
332 * @param attitude attitude
333 * @param mass the mass (kg)
334 * @exception IllegalArgumentException if orbit and attitude dates
335 * or frames are not equal
336 */
337 public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude, final T mass)
338 throws IllegalArgumentException {
339 this(absPva, attitude, mass, null);
340 }
341
342 /** Build a spacecraft state from orbit only.
343 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
344 * @param absPva position-velocity-acceleration
345 * @param additional additional states
346 */
347 public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final Map<String, T[]> additional) {
348 this(absPva,
349 new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
350 absPva.getDate().getField().getZero().add(DEFAULT_MASS), additional);
351 }
352
353 /** Build a spacecraft state from orbit and attitude provider.
354 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
355 * @param absPva position-velocity-acceleration
356 * @param attitude attitude
357 * @param additional additional states
358 * @exception IllegalArgumentException if orbit and attitude dates
359 * or frames are not equal
360 */
361 public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude, final Map<String, T[]> additional)
362 throws IllegalArgumentException {
363 this(absPva, attitude, absPva.getDate().getField().getZero().add(DEFAULT_MASS), additional);
364 }
365
366 /** Create a new instance from orbit and mass.
367 * <p>Attitude law is set to an unspecified default attitude.</p>
368 * @param absPva position-velocity-acceleration
369 * @param mass the mass (kg)
370 * @param additional additional states
371 */
372 public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final T mass, final Map<String, T[]> additional) {
373 this(absPva,
374 new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
375 mass, additional);
376 }
377
378 /** Build a spacecraft state from orbit, attitude provider and mass.
379 * @param absPva position-velocity-acceleration
380 * @param attitude attitude
381 * @param mass the mass (kg)
382 * @param additional additional states (may be null if no additional states are available)
383 * @exception IllegalArgumentException if orbit and attitude dates
384 * or frames are not equal
385 */
386 public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude,
387 final T mass, final Map<String, T[]> additional)
388 throws IllegalArgumentException {
389 checkConsistency(absPva, attitude);
390 this.orbit = null;
391 this.absPva = absPva;
392 this.attitude = attitude;
393 this.mass = mass;
394 if (additional == null) {
395 this.additional = Collections.emptyMap();
396 } else {
397 this.additional = new HashMap<String, T[]>(additional.size());
398 for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
399 this.additional.put(entry.getKey(), entry.getValue().clone());
400 }
401 }
402 }
403
404 /** Add an additional state.
405 * <p>
406 * {@link FieldSpacecraftState SpacecraftState} instances are immutable,
407 * so this method does <em>not</em> change the instance, but rather
408 * creates a new instance, which has the same orbit, attitude, mass
409 * and additional states as the original instance, except it also
410 * has the specified state. If the original instance already had an
411 * additional state with the same name, it will be overridden. If it
412 * did not have any additional state with that name, the new instance
413 * will have one more additional state than the original instance.
414 * </p>
415 * @param name name of the additional state
416 * @param value value of the additional state
417 * @return a new instance, with the additional state added
418 * @see #hasAdditionalState(String)
419 * @see #getAdditionalState(String)
420 * @see #getAdditionalStates()
421 */
422 @SafeVarargs
423 public final FieldSpacecraftState<T> addAdditionalState(final String name, final T... value) {
424 final Map<String, T[]> newMap = new HashMap<String, T[]>(additional.size() + 1);
425 newMap.putAll(additional);
426 newMap.put(name, value.clone());
427 if (absPva == null) {
428 return new FieldSpacecraftState<>(orbit, attitude, mass, newMap);
429 } else {
430 return new FieldSpacecraftState<>(absPva, attitude, mass, newMap);
431 }
432 }
433
434 /** Check orbit and attitude dates are equal.
435 * @param orbitN the orbit
436 * @param attitudeN attitude
437 * @exception IllegalArgumentException if orbit and attitude dates
438 * are not equal
439 */
440 private void checkConsistency(final FieldOrbit<T> orbitN, final FieldAttitude<T> attitudeN)
441 throws IllegalArgumentException {
442 if (orbitN.getDate().durationFrom(attitudeN.getDate()).abs().getReal() >
443 DATE_INCONSISTENCY_THRESHOLD) {
444
445 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
446 orbitN.getDate(), attitudeN.getDate());
447 }
448
449 if (orbitN.getFrame() != attitudeN.getReferenceFrame()) {
450 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
451 orbitN.getFrame().getName(),
452 attitudeN.getReferenceFrame().getName());
453 }
454 }
455
456 /** Check if the state contains an orbit part.
457 * <p>
458 * A state contains either an {@link FieldAbsolutePVCoordinates absolute
459 * position-velocity-acceleration} or an {@link FieldOrbit orbit}.
460 * </p>
461 * @return true if state contains an orbit (in which case {@link #getOrbit()}
462 * will not throw an exception), or false if the state contains an
463 * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
464 * will not throw an exception)
465 */
466 public boolean isOrbitDefined() {
467 return orbit != null;
468 }
469
470 /**
471 * Check FieldAbsolutePVCoordinates and attitude dates are equal.
472 * @param absPva position-velocity-acceleration
473 * @param attitude attitude
474 * @param <T> the type of the field elements
475 * @exception IllegalArgumentException if orbit and attitude dates are not equal
476 */
477 private static <T extends CalculusFieldElement<T>> void checkConsistency(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
478 throws IllegalArgumentException {
479 if (FastMath.abs(absPva.getDate().durationFrom(attitude.getDate())).getReal() >
480 DATE_INCONSISTENCY_THRESHOLD) {
481 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
482 absPva.getDate(), attitude.getDate());
483 }
484 if (absPva.getFrame() != attitude.getReferenceFrame()) {
485 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
486 absPva.getFrame().getName(),
487 attitude.getReferenceFrame().getName());
488 }
489 }
490
491 /** Get a time-shifted state.
492 * <p>
493 * The state can be slightly shifted to close dates. This shift is based on
494 * a simple Keplerian model for orbit, a linear extrapolation for attitude
495 * taking the spin rate into account and neither mass nor additional states
496 * changes. It is <em>not</em> intended as a replacement for proper orbit
497 * and attitude propagation but should be sufficient for small time shifts
498 * or coarse accuracy.
499 * </p>
500 * <p>
501 * As a rough order of magnitude, the following table shows the extrapolation
502 * errors obtained between this simple shift method and an {@link
503 * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
504 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
505 * Sun and Moon third bodies attractions, drag and solar radiation pressure.
506 * Beware that these results will be different for other orbits.
507 * </p>
508 * <table border="1">
509 * <caption>Extrapolation Error</caption>
510 * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
511 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
512 * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td> 18</td><td> 1.1</td></tr>
513 * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td> 72</td><td> 9.1</td></tr>
514 * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
515 * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
516 * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
517 * </table>
518 * @param dt time shift in seconds
519 * @return a new state, shifted with respect to the instance (which is immutable)
520 * except for the mass which stay unchanged
521 */
522 public FieldSpacecraftState<T> shiftedBy(final double dt) {
523 if (absPva == null) {
524 return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
525 mass, additional);
526 } else {
527 return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
528 mass, additional);
529 }
530 }
531
532 /** Get a time-shifted state.
533 * <p>
534 * The state can be slightly shifted to close dates. This shift is based on
535 * a simple Keplerian model for orbit, a linear extrapolation for attitude
536 * taking the spin rate into account and neither mass nor additional states
537 * changes. It is <em>not</em> intended as a replacement for proper orbit
538 * and attitude propagation but should be sufficient for small time shifts
539 * or coarse accuracy.
540 * </p>
541 * <p>
542 * As a rough order of magnitude, the following table shows the extrapolation
543 * errors obtained between this simple shift method and an {@link
544 * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
545 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
546 * Sun and Moon third bodies attractions, drag and solar radiation pressure.
547 * Beware that these results will be different for other orbits.
548 * </p>
549 * <table border="1">
550 * <caption>Extrapolation Error</caption>
551 * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
552 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
553 * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td> 18</td><td> 1.1</td></tr>
554 * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td> 72</td><td> 9.1</td></tr>
555 * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
556 * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
557 * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
558 * </table>
559 * @param dt time shift in seconds
560 * @return a new state, shifted with respect to the instance (which is immutable)
561 * except for the mass which stay unchanged
562 */
563 public FieldSpacecraftState<T> shiftedBy(final T dt) {
564 if (absPva == null) {
565 return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
566 mass, additional);
567 } else {
568 return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
569 mass, additional);
570 }
571 }
572
573 /** {@inheritDoc}
574 * <p>
575 * The additional states that are interpolated are the ones already present
576 * in the instance. The sample instances must therefore have at least the same
577 * additional states has the instance. They may have more additional states,
578 * but the extra ones will be ignored.
579 * </p>
580 * <p>
581 * The instance and all the sample instances <em>must</em> be based on similar
582 * trajectory data, i.e. they must either all be based on orbits or all be based
583 * on absolute position-velocity-acceleration. Any inconsistency will trigger
584 * an {@link OrekitIllegalStateException}.
585 * </p>
586 * <p>
587 * As this implementation of interpolation is polynomial, it should be used only
588 * with small samples (about 10-20 points) in order to avoid <a
589 * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
590 * and numerical problems (including NaN appearing).
591 * </p>
592 * @exception OrekitIllegalStateException if some instances are not based on
593 * similar trajectory data
594 */
595 public FieldSpacecraftState<T> interpolate(final FieldAbsoluteDate<T> date,
596 final Stream<FieldSpacecraftState<T>> sample) {
597
598 // prepare interpolators
599 final List<FieldOrbit<T>> orbits;
600 final List<FieldAbsolutePVCoordinates<T>> absPvas;
601 if (isOrbitDefined()) {
602 orbits = new ArrayList<FieldOrbit<T>>();
603 absPvas = null;
604 } else {
605 orbits = null;
606 absPvas = new ArrayList<FieldAbsolutePVCoordinates<T>>();
607 }
608 final List<FieldAttitude<T>> attitudes = new ArrayList<>();
609 final FieldHermiteInterpolator<T> massInterpolator = new FieldHermiteInterpolator<>();
610 final Map<String, FieldHermiteInterpolator<T>> additionalInterpolators =
611 new HashMap<String, FieldHermiteInterpolator<T>>(additional.size());
612 for (final String name : additional.keySet()) {
613 additionalInterpolators.put(name, new FieldHermiteInterpolator<>());
614 }
615
616 // extract sample data
617 sample.forEach(state -> {
618 final T deltaT = state.getDate().durationFrom(date);
619 if (isOrbitDefined()) {
620 orbits.add(state.getOrbit());
621 } else {
622 absPvas.add(state.getAbsPVA());
623 }
624 attitudes.add(state.getAttitude());
625 final T[] mm = MathArrays.buildArray(date.getField(), 1);
626 mm[0] = state.getMass();
627 massInterpolator.addSamplePoint(deltaT,
628 mm);
629 for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalInterpolators.entrySet()) {
630 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
631 }
632 });
633
634 // perform interpolations
635 final FieldOrbit<T> interpolatedOrbit;
636 final FieldAbsolutePVCoordinates<T> interpolatedAbsPva;
637 if (isOrbitDefined()) {
638 interpolatedOrbit = orbit.interpolate(date, orbits);
639 interpolatedAbsPva = null;
640 } else {
641 interpolatedOrbit = null;
642 interpolatedAbsPva = absPva.interpolate(date, absPvas);
643 }
644 final FieldAttitude<T> interpolatedAttitude = attitude.interpolate(date, attitudes);
645 final T interpolatedMass = massInterpolator.value(date.getField().getZero())[0];
646 final Map<String, T[]> interpolatedAdditional;
647 if (additional.isEmpty()) {
648 interpolatedAdditional = null;
649 } else {
650 interpolatedAdditional = new HashMap<String, T[]>(additional.size());
651 for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalInterpolators.entrySet()) {
652 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(date.getField().getZero()));
653 }
654 }
655
656 // create the complete interpolated state
657 if (isOrbitDefined()) {
658 return new FieldSpacecraftState<>(interpolatedOrbit, interpolatedAttitude,
659 interpolatedMass, interpolatedAdditional);
660 } else {
661 return new FieldSpacecraftState<>(interpolatedAbsPva, interpolatedAttitude,
662 interpolatedMass, interpolatedAdditional);
663 }
664
665 }
666
667 /** Get the absolute position-velocity-acceleration.
668 * <p>
669 * A state contains either an {@link FieldAbsolutePVCoordinates absolute
670 * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
671 * one is present can be checked using {@link #isOrbitDefined()}.
672 * </p>
673 * @return absolute position-velocity-acceleration
674 * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
675 * which mean the state rather contains an {@link FieldOrbit}
676 * @see #isOrbitDefined()
677 * @see #getOrbit()
678 */
679 public FieldAbsolutePVCoordinates<T> getAbsPVA() throws OrekitIllegalStateException {
680 if (absPva == null) {
681 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
682 }
683 return absPva;
684 }
685
686 /** Get the current orbit.
687 * <p>
688 * A state contains either an {@link FieldAbsolutePVCoordinates absolute
689 * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
690 * one is present can be checked using {@link #isOrbitDefined()}.
691 * </p>
692 * @return the orbit
693 * @exception OrekitIllegalStateException if orbit is null,
694 * which means the state rather contains an {@link FieldAbsolutePVCoordinates absolute
695 * position-velocity-acceleration}
696 * @see #isOrbitDefined()
697 * @see #getAbsPVA()
698 */
699 public FieldOrbit<T> getOrbit() throws OrekitIllegalStateException {
700 if (orbit == null) {
701 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
702 }
703 return orbit;
704 }
705
706 /** Get the date.
707 * @return date
708 */
709 public FieldAbsoluteDate<T> getDate() {
710 return (absPva == null) ? orbit.getDate() : absPva.getDate();
711 }
712
713 /** Get the defining frame.
714 * @return the frame in which state is defined
715 */
716 public Frame getFrame() {
717 return (absPva == null) ? orbit.getFrame() : absPva.getFrame();
718 }
719
720
721 /** Check if an additional state is available.
722 * @param name name of the additional state
723 * @return true if the additional state is available
724 * @see #addAdditionalState(String, CalculusFieldElement...)
725 * @see #getAdditionalState(String)
726 * @see #getAdditionalStates()
727 */
728 public boolean hasAdditionalState(final String name) {
729 return additional.containsKey(name);
730 }
731
732 /** Check if two instances have the same set of additional states available.
733 * <p>
734 * Only the names and dimensions of the additional states are compared,
735 * not their values.
736 * </p>
737 * @param state state to compare to instance
738 * @exception MathIllegalArgumentException if an additional state does not have
739 * the same dimension in both states
740 */
741 public void ensureCompatibleAdditionalStates(final FieldSpacecraftState<T> state)
742 throws MathIllegalArgumentException {
743
744 // check instance additional states is a subset of the other one
745 for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
746 final T[] other = state.additional.get(entry.getKey());
747 if (other == null) {
748 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
749 entry.getKey());
750 }
751 if (other.length != entry.getValue().length) {
752 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
753 other.length, entry.getValue().length);
754 }
755 }
756
757 if (state.additional.size() > additional.size()) {
758 // the other state has more additional states
759 for (final String name : state.additional.keySet()) {
760 if (!additional.containsKey(name)) {
761 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
762 name);
763 }
764 }
765 }
766
767 }
768
769 /** Get an additional state.
770 * @param name name of the additional state
771 * @return value of the additional state
772 * @see #addAdditionalState(String, CalculusFieldElement...)
773 * @see #hasAdditionalState(String)
774 * @see #getAdditionalStates()
775 */
776 public T[] getAdditionalState(final String name) {
777 if (!additional.containsKey(name)) {
778 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
779 }
780 return additional.get(name).clone();
781 }
782
783 /** Get an unmodifiable map of additional states.
784 * @return unmodifiable map of additional states
785 * @see #addAdditionalState(String, CalculusFieldElement...)
786 * @see #hasAdditionalState(String)
787 * @see #getAdditionalState(String)
788 */
789 public Map<String, T[]> getAdditionalStates() {
790 return Collections.unmodifiableMap(additional);
791 }
792
793 /** Compute the transform from state defining frame to spacecraft frame.
794 * <p>The spacecraft frame origin is at the point defined by the orbit,
795 * and its orientation is defined by the attitude.</p>
796 * @return transform from specified frame to current spacecraft frame
797 */
798 public FieldTransform<T> toTransform() {
799 final TimeStampedFieldPVCoordinates<T> pv = getPVCoordinates();
800 return new FieldTransform<>(pv.getDate(),
801 new FieldTransform<>(pv.getDate(), pv.negate()),
802 new FieldTransform<>(pv.getDate(), attitude.getOrientation()));
803 }
804
805 /** Get the central attraction coefficient.
806 * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the
807 * state is contains an absolute position-velocity-acceleration rather than an orbit
808 */
809 public T getMu() {
810 return (absPva == null) ? orbit.getMu() : absPva.getDate().getField().getZero().add(Double.NaN);
811 }
812
813 /** Get the Keplerian period.
814 * <p>The Keplerian period is computed directly from semi major axis
815 * and central acceleration constant.</p>
816 * @return keplerian period in seconds, or {code Double.NaN} if the
817 * state is contains an absolute position-velocity-acceleration rather
818 * than an orbit
819 */
820 public T getKeplerianPeriod() {
821 return (absPva == null) ? orbit.getKeplerianPeriod() : absPva.getDate().getField().getZero().add(Double.NaN);
822 }
823
824 /** Get the Keplerian mean motion.
825 * <p>The Keplerian mean motion is computed directly from semi major axis
826 * and central acceleration constant.</p>
827 * @return keplerian mean motion in radians per second, or {code Double.NaN} if the
828 * state is contains an absolute position-velocity-acceleration rather
829 * than an orbit
830 */
831 public T getKeplerianMeanMotion() {
832 return (absPva == null) ? orbit.getKeplerianMeanMotion() : absPva.getDate().getField().getZero().add(Double.NaN);
833 }
834
835 /** Get the semi-major axis.
836 * @return semi-major axis (m), or {code Double.NaN} if the
837 * state is contains an absolute position-velocity-acceleration rather
838 * than an orbit
839 */
840 public T getA() {
841 return (absPva == null) ? orbit.getA() : absPva.getDate().getField().getZero().add(Double.NaN);
842 }
843
844 /** Get the first component of the eccentricity vector (as per equinoctial parameters).
845 * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the
846 * state is contains an absolute position-velocity-acceleration rather
847 * than an orbit
848 * @see #getE()
849 */
850 public T getEquinoctialEx() {
851 return (absPva == null) ? orbit.getEquinoctialEx() : absPva.getDate().getField().getZero().add(Double.NaN);
852 }
853
854 /** Get the second component of the eccentricity vector (as per equinoctial parameters).
855 * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the
856 * state is contains an absolute position-velocity-acceleration rather
857 * than an orbit
858 * @see #getE()
859 */
860 public T getEquinoctialEy() {
861 return (absPva == null) ? orbit.getEquinoctialEy() : absPva.getDate().getField().getZero().add(Double.NaN);
862 }
863
864 /** Get the first component of the inclination vector (as per equinoctial parameters).
865 * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the
866 * state is contains an absolute position-velocity-acceleration rather
867 * than an orbit
868 * @see #getI()
869 */
870 public T getHx() {
871 return (absPva == null) ? orbit.getHx() : absPva.getDate().getField().getZero().add(Double.NaN);
872 }
873
874 /** Get the second component of the inclination vector (as per equinoctial parameters).
875 * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the
876 * state is contains an absolute position-velocity-acceleration rather
877 * than an orbit
878 * @see #getI()
879 */
880 public T getHy() {
881 return (absPva == null) ? orbit.getHy() : absPva.getDate().getField().getZero().add(Double.NaN);
882 }
883
884 /** Get the true latitude argument (as per equinoctial parameters).
885 * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the
886 * state is contains an absolute position-velocity-acceleration rather
887 * than an orbit
888 * @see #getLE()
889 * @see #getLM()
890 */
891 public T getLv() {
892 return (absPva == null) ? orbit.getLv() : absPva.getDate().getField().getZero().add(Double.NaN);
893 }
894
895 /** Get the eccentric latitude argument (as per equinoctial parameters).
896 * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the
897 * state is contains an absolute position-velocity-acceleration rather
898 * than an orbit
899 * @see #getLv()
900 * @see #getLM()
901 */
902 public T getLE() {
903 return (absPva == null) ? orbit.getLE() : absPva.getDate().getField().getZero().add(Double.NaN);
904 }
905
906 /** Get the mean longitude argument (as per equinoctial parameters).
907 * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the
908 * state is contains an absolute position-velocity-acceleration rather
909 * than an orbit
910 * @see #getLv()
911 * @see #getLE()
912 */
913 public T getLM() {
914 return (absPva == null) ? orbit.getLM() : absPva.getDate().getField().getZero().add(Double.NaN);
915 }
916
917 // Additional orbital elements
918
919 /** Get the eccentricity.
920 * @return eccentricity, or {code Double.NaN} if the
921 * state is contains an absolute position-velocity-acceleration rather
922 * than an orbit
923 * @see #getEquinoctialEx()
924 * @see #getEquinoctialEy()
925 */
926 public T getE() {
927 return (absPva == null) ? orbit.getE() : absPva.getDate().getField().getZero().add(Double.NaN);
928 }
929
930 /** Get the inclination.
931 * @return inclination (rad)
932 * @see #getHx()
933 * @see #getHy()
934 */
935 public T getI() {
936 return (absPva == null) ? orbit.getI() : absPva.getDate().getField().getZero().add(Double.NaN);
937 }
938
939 /** Get the {@link TimeStampedFieldPVCoordinates} in orbit definition frame.
940 * <p>
941 * Compute the position and velocity of the satellite. This method caches its
942 * results, and recompute them only when the method is called with a new value
943 * for mu. The result is provided as a reference to the internally cached
944 * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
945 * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
946 * </p>
947 * @return pvCoordinates in orbit definition frame
948 */
949 public TimeStampedFieldPVCoordinates<T> getPVCoordinates() {
950 return (absPva == null) ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
951 }
952
953 /** Get the {@link TimeStampedFieldPVCoordinates} in given output frame.
954 * <p>
955 * Compute the position and velocity of the satellite. This method caches its
956 * results, and recompute them only when the method is called with a new value
957 * for mu. The result is provided as a reference to the internally cached
958 * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
959 * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
960 * </p>
961 * @param outputFrame frame in which coordinates should be defined
962 * @return pvCoordinates in orbit definition frame
963 */
964 public TimeStampedFieldPVCoordinates<T>getPVCoordinates(final Frame outputFrame) {
965 return (absPva == null) ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
966 }
967
968 /** Get the attitude.
969 * @return the attitude.
970 */
971 public FieldAttitude<T> getAttitude() {
972 return attitude;
973 }
974
975 /** Gets the current mass.
976 * @return the mass (kg)
977 */
978 public T getMass() {
979 return mass;
980 }
981
982 /**To convert a FieldSpacecraftState instance into a SpacecraftState instance.
983 *
984 * @return SpacecraftState instance with the same properties
985 */
986 public SpacecraftState toSpacecraftState() {
987 final Map<String, double[]> map;
988 if (additional.isEmpty()) {
989 map = Collections.emptyMap();
990 } else {
991 map = new HashMap<>(additional.size());
992 for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
993 final double[] array = new double[entry.getValue().length];
994 for (int k = 0; k < array.length; ++k) {
995 array[k] = entry.getValue()[k].getReal();
996 }
997 map.put(entry.getKey(), array);
998 }
999 }
1000 if (isOrbitDefined()) {
1001 return new SpacecraftState(orbit.toOrbit(), attitude.toAttitude(),
1002 mass.getReal(), map);
1003 } else {
1004 return new SpacecraftState(absPva.toAbsolutePVCoordinates(),
1005 attitude.toAttitude(), mass.getReal(),
1006 map);
1007 }
1008 }
1009
1010 @Override
1011 public String toString() {
1012 return "FieldSpacecraftState{" +
1013 "orbit=" + orbit +
1014 ", attitude=" + attitude +
1015 ", mass=" + mass +
1016 ", additional=" + additional +
1017 '}';
1018 }
1019
1020 }