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