1 /* Copyright 2002-2025 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 org.hipparchus.exception.LocalizedCoreFormats;
20 import org.hipparchus.exception.MathIllegalStateException;
21 import org.hipparchus.geometry.euclidean.threed.Vector3D;
22 import org.hipparchus.util.FastMath;
23 import org.orekit.attitudes.Attitude;
24 import org.orekit.attitudes.AttitudeProvider;
25 import org.orekit.attitudes.FrameAlignedProvider;
26 import org.orekit.errors.OrekitException;
27 import org.orekit.errors.OrekitIllegalArgumentException;
28 import org.orekit.errors.OrekitIllegalStateException;
29 import org.orekit.errors.OrekitMessages;
30 import org.orekit.frames.Frame;
31 import org.orekit.frames.StaticTransform;
32 import org.orekit.frames.Transform;
33 import org.orekit.orbits.Orbit;
34 import org.orekit.propagation.numerical.NumericalPropagator;
35 import org.orekit.time.AbsoluteDate;
36 import org.orekit.time.TimeOffset;
37 import org.orekit.time.TimeShiftable;
38 import org.orekit.time.TimeStamped;
39 import org.orekit.utils.AbsolutePVCoordinates;
40 import org.orekit.utils.DataDictionary;
41 import org.orekit.utils.DoubleArrayDictionary;
42 import org.orekit.utils.TimeStampedPVCoordinates;
43
44 /** This class is the representation of a complete state holding orbit, attitude
45 * and mass information at a given date, meant primarily for propagation.
46 *
47 * <p>It contains an {@link Orbit}, or an {@link AbsolutePVCoordinates} if there
48 * is no definite central body, plus the current mass and attitude at the intrinsic
49 * {@link AbsoluteDate}. Quantities are guaranteed to be consistent in terms
50 * of date and reference frame. The spacecraft state may also contain additional
51 * data, which are simply named.
52 * </p>
53 * <p>
54 * The state can be slightly shifted to close dates. This actual shift varies
55 * between {@link Orbit} and {@link AbsolutePVCoordinates}.
56 * For attitude it is a linear extrapolation taking the spin rate into account
57 * and no mass change. It is <em>not</em> intended as a replacement for proper
58 * orbit and attitude propagation but should be sufficient for either small
59 * time shifts or coarse accuracy.
60 * </p>
61 * <p>
62 * The instance <code>SpacecraftState</code> is guaranteed to be immutable.
63 * </p>
64 * @see NumericalPropagator
65 * @author Fabien Maussion
66 * @author Véronique Pommier-Maurussane
67 * @author Luc Maisonobe
68 */
69 public class SpacecraftState implements TimeStamped, TimeShiftable<SpacecraftState> {
70
71 /** Default mass. */
72 public static final double DEFAULT_MASS = 1000.0;
73
74 /**
75 * tolerance on date comparison in {@link #checkConsistency(Orbit, Attitude)}. 100 ns
76 * corresponds to sub-mm accuracy at LEO orbital velocities.
77 */
78 private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;
79
80 /** Orbital state. */
81 private final Orbit orbit;
82
83 /** Trajectory state, when it is not an orbit. */
84 private final AbsolutePVCoordinates absPva;
85
86 /** Attitude. */
87 private final Attitude attitude;
88
89 /** Current mass (kg). */
90 private final double mass;
91
92 /** Additional data, can be any object (String, double[], etc.). */
93 private final DataDictionary additional;
94
95 /** Additional states derivatives.
96 * @since 11.1
97 */
98 private final DoubleArrayDictionary additionalDot;
99
100 /** Build a spacecraft state from orbit only.
101 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
102 * @param orbit the orbit
103 */
104 public SpacecraftState(final Orbit orbit) {
105 this(orbit, getDefaultAttitudeProvider(orbit.getFrame())
106 .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
107 DEFAULT_MASS, null, null);
108 }
109
110 /** Build a spacecraft state from orbit and attitude. Kept for performance.
111 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
112 * @param orbit the orbit
113 * @param attitude attitude
114 * @exception IllegalArgumentException if orbit and attitude dates
115 * or frames are not equal
116 */
117 public SpacecraftState(final Orbit orbit, final Attitude attitude)
118 throws IllegalArgumentException {
119 this(orbit, attitude, DEFAULT_MASS, null, null);
120 checkConsistency(orbit, attitude);
121 }
122
123 /** Create a new instance from orbit and mass.
124 * <p>Attitude law is set to an unspecified default attitude.</p>
125 * @param orbit the orbit
126 * @param mass the mass (kg)
127 * @deprecated since 13.0, use withXXX
128 */
129 @Deprecated
130 public SpacecraftState(final Orbit orbit, final double mass) {
131 this(orbit, getDefaultAttitudeProvider(orbit.getFrame())
132 .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
133 mass, null, null);
134 }
135
136 /** Build a spacecraft state from orbit, attitude and mass.
137 * @param orbit the orbit
138 * @param attitude attitude
139 * @param mass the mass (kg)
140 * @exception IllegalArgumentException if orbit and attitude dates
141 * or frames are not equal
142 * @deprecated since 13.0, use withXXX
143 */
144 @Deprecated
145 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass)
146 throws IllegalArgumentException {
147 this(orbit, attitude, mass, null, null);
148 checkConsistency(orbit, attitude);
149 }
150
151 /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
152 * @param orbit the orbit
153 * @param attitude attitude
154 * @param mass the mass (kg)
155 * @param additional additional data (may be null if no additional states are available)
156 * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available)
157 * @exception IllegalArgumentException if orbit and attitude dates
158 * or frames are not equal
159 * @since 13.0
160 */
161 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass,
162 final DataDictionary additional, final DoubleArrayDictionary additionalDot)
163 throws IllegalArgumentException {
164 this(orbit, null, attitude, mass, additional, additionalDot, true);
165 checkConsistency(orbit, attitude);
166 }
167
168 /** Build a spacecraft state from position-velocity-acceleration only.
169 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
170 * @param absPva position-velocity-acceleration
171 */
172 public SpacecraftState(final AbsolutePVCoordinates absPva) {
173 this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
174 .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
175 DEFAULT_MASS, null, null);
176 }
177
178 /** Build a spacecraft state from position-velocity-acceleration and attitude. Kept for performance.
179 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
180 * @param absPva position-velocity-acceleration
181 * @param attitude attitude
182 * @exception IllegalArgumentException if orbit and attitude dates
183 * or frames are not equal
184 */
185 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude)
186 throws IllegalArgumentException {
187 this(absPva, attitude, DEFAULT_MASS, null, null);
188 checkConsistency(absPva, attitude);
189 }
190
191 /** Create a new instance from position-velocity-acceleration and mass.
192 * <p>Attitude law is set to an unspecified default attitude.</p>
193 * @param absPva position-velocity-acceleration
194 * @param mass the mass (kg)
195 * @deprecated since 13.0, use withXXX
196 */
197 @Deprecated
198 public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) {
199 this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
200 .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
201 mass, null, null);
202 }
203
204 /** Build a spacecraft state from position-velocity-acceleration, attitude and mass.
205 * @param absPva position-velocity-acceleration
206 * @param attitude attitude
207 * @param mass the mass (kg)
208 * @exception IllegalArgumentException if orbit and attitude dates
209 * or frames are not equal
210 * @deprecated since 13.0, use withXXX
211 */
212 @Deprecated
213 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass)
214 throws IllegalArgumentException {
215 this(absPva, attitude, mass, null, null);
216 checkConsistency(absPva, attitude);
217 }
218
219 /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states and derivatives.
220 * @param absPva position-velocity-acceleration
221 * @param attitude attitude
222 * @param mass the mass (kg)
223 * @param additional additional data (may be null if no additional data are available)
224 * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available)
225 * @exception IllegalArgumentException if orbit and attitude dates
226 * or frames are not equal
227 * @since 13.0
228 */
229 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass,
230 final DataDictionary additional, final DoubleArrayDictionary additionalDot)
231 throws IllegalArgumentException {
232 this(null, absPva, attitude, mass, additional, additionalDot, true);
233 checkConsistency(absPva, attitude);
234 }
235
236 /** Full, private constructor.
237 * @param orbit the orbit
238 * @param absPva absolute position-velocity
239 * @param attitude attitude
240 * @param mass the mass (kg)
241 * @param additional additional data (may be null if no additional states are available)
242 * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available)
243 * @param deepCopy flag to copy dictionaries (additional data and derivatives)
244 * @exception IllegalArgumentException if orbit and attitude dates
245 * or frames are not equal
246 * @since 13.1.1
247 */
248 private SpacecraftState(final Orbit orbit, final AbsolutePVCoordinates absPva,
249 final Attitude attitude, final double mass,
250 final DataDictionary additional, final DoubleArrayDictionary additionalDot,
251 final boolean deepCopy)
252 throws IllegalArgumentException {
253 this.orbit = orbit;
254 this.absPva = absPva;
255 this.attitude = attitude;
256 this.mass = mass;
257 if (deepCopy) {
258 this.additional = additional == null ? new DataDictionary() : new DataDictionary(additional);
259 this.additionalDot = additionalDot == null ? new DoubleArrayDictionary() : new DoubleArrayDictionary(additionalDot);
260 } else {
261 this.additional = additional == null ? new DataDictionary() : additional;
262 this.additionalDot = additionalDot == null ? new DoubleArrayDictionary() : additionalDot;
263 }
264 }
265
266 /**
267 * Create a new instance with input mass.
268 * @param newMass mass
269 * @return new state
270 * @since 13.0
271 */
272 public SpacecraftState withMass(final double newMass) {
273 return new SpacecraftState(orbit, absPva, attitude, newMass, additional, additionalDot, false);
274 }
275
276 /**
277 * Create a new instance with input attitude.
278 * @param newAttitude attitude
279 * @return new state
280 * @since 13.0
281 */
282 public SpacecraftState withAttitude(final Attitude newAttitude) {
283 if (isOrbitDefined()) {
284 checkConsistency(orbit, newAttitude);
285 } else {
286 checkConsistency(absPva, newAttitude);
287 }
288 return new SpacecraftState(orbit, absPva, newAttitude, mass, additional, additionalDot, false);
289 }
290
291 /**
292 * Create a new instance with input additional data.
293 * @param dataDictionary additional data
294 * @return new state
295 * @since 13.0
296 */
297 public SpacecraftState withAdditionalData(final DataDictionary dataDictionary) {
298 return new SpacecraftState(orbit, absPva, attitude, mass, dataDictionary, additionalDot, false);
299 }
300
301 /**
302 * Create a new instance with input additional data.
303 * @param additionalStateDerivatives additional state derivatives
304 * @return new state
305 * @since 13.0
306 */
307 public SpacecraftState withAdditionalStatesDerivatives(final DoubleArrayDictionary additionalStateDerivatives) {
308 return new SpacecraftState(orbit, absPva, attitude, mass, additional, additionalStateDerivatives, false);
309 }
310
311 /** Add an additional data.
312 * <p>
313 * {@link SpacecraftState SpacecraftState} instances are immutable,
314 * so this method does <em>not</em> change the instance, but rather
315 * creates a new instance, which has the same orbit, attitude, mass
316 * and additional states as the original instance, except it also
317 * has the specified state. If the original instance already had an
318 * additional data with the same name, it will be overridden. If it
319 * did not have any additional state with that name, the new instance
320 * will have one more additional state than the original instance.
321 * </p>
322 * @param name name of the additional data (names containing "orekit"
323 * with any case are reserved for the library internal use)
324 * @param value value of the additional data
325 * @return a new instance, with the additional data added
326 * @see #hasAdditionalData(String)
327 * @see #getAdditionalData(String)
328 * @see #getAdditionalDataValues()
329 * @since 13.0
330 */
331 public SpacecraftState addAdditionalData(final String name, final Object value) {
332 final DataDictionary newDict = new DataDictionary(additional);
333 if (value instanceof double[]) {
334 newDict.put(name, ((double[]) value).clone());
335 } else if (value instanceof Double) {
336 newDict.put(name, new double[] {(double) value});
337 } else {
338 newDict.put(name, value);
339 }
340 return withAdditionalData(newDict);
341 }
342
343 /** Add an additional state derivative.
344 * <p>
345 * {@link SpacecraftState SpacecraftState} instances are immutable,
346 * so this method does <em>not</em> change the instance, but rather
347 * creates a new instance, which has the same components as the original
348 * instance, except it also has the specified state derivative. If the
349 * original instance already had an additional state derivative with the
350 * same name, it will be overridden. If it did not have any additional
351 * state derivative with that name, the new instance will have one more
352 * additional state derivative than the original instance.
353 * </p>
354 * @param name name of the additional state derivative (names containing "orekit"
355 * with any case are reserved for the library internal use)
356 * @param value value of the additional state derivative
357 * @return a new instance, with the additional state added
358 * @see #hasAdditionalStateDerivative(String)
359 * @see #getAdditionalStateDerivative(String)
360 * @see #getAdditionalStatesDerivatives()
361 * @since 11.1
362 */
363 public SpacecraftState addAdditionalStateDerivative(final String name, final double... value) {
364 final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additionalDot);
365 newDict.put(name, value.clone());
366 return withAdditionalStatesDerivatives(newDict);
367 }
368
369 /** Check orbit and attitude dates are equal.
370 * @param orbit the orbit
371 * @param attitude attitude
372 * @exception IllegalArgumentException if orbit and attitude dates
373 * are not equal
374 */
375 private static void checkConsistency(final Orbit orbit, final Attitude attitude)
376 throws IllegalArgumentException {
377 checkDateAndFrameConsistency(attitude, orbit.getDate(), orbit.getFrame());
378 }
379
380 /** Defines provider for default Attitude when not passed to constructor.
381 * Currently chosen arbitrarily as aligned with input frame.
382 * It is also used in {@link FieldSpacecraftState}.
383 * @param frame reference frame
384 * @return default attitude provider
385 * @since 12.0
386 */
387 static AttitudeProvider getDefaultAttitudeProvider(final Frame frame) {
388 return new FrameAlignedProvider(frame);
389 }
390
391 /** Check if the state contains an orbit part.
392 * <p>
393 * A state contains either an {@link AbsolutePVCoordinates absolute
394 * position-velocity-acceleration} or an {@link Orbit orbit}.
395 * </p>
396 * @return true if state contains an orbit (in which case {@link #getOrbit()}
397 * will not throw an exception), or false if the state contains an
398 * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
399 * will not throw an exception)
400 */
401 public boolean isOrbitDefined() {
402 return orbit != null;
403 }
404
405 /** Check AbsolutePVCoordinates and attitude dates are equal.
406 * @param absPva position-velocity-acceleration
407 * @param attitude attitude
408 * @exception IllegalArgumentException if orbit and attitude dates
409 * are not equal
410 */
411 private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude)
412 throws IllegalArgumentException {
413 checkDateAndFrameConsistency(attitude, absPva.getDate(), absPva.getFrame());
414 }
415
416 /** Check attitude frame and epoch.
417 * @param attitude attitude
418 * @param date epoch to verify
419 * @param frame frame to verify
420 */
421 private static void checkDateAndFrameConsistency(final Attitude attitude, final AbsoluteDate date, final Frame frame) {
422 if (FastMath.abs(date.durationFrom(attitude.getDate())) >
423 DATE_INCONSISTENCY_THRESHOLD) {
424 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
425 date, attitude.getDate());
426 }
427 if (frame != attitude.getReferenceFrame()) {
428 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
429 frame.getName(),
430 attitude.getReferenceFrame().getName());
431 }
432 }
433
434 /** Get a time-shifted state.
435 * <p>
436 * The state can be slightly shifted to close dates. This shift is based on
437 * simple models. For orbits, the model is a Keplerian one if no derivatives
438 * are available in the orbit, or Keplerian plus quadratic effect of the
439 * non-Keplerian acceleration if derivatives are available. For attitude,
440 * a polynomial model is used. Neither mass nor additional states change.
441 * Shifting is <em>not</em> intended as a replacement for proper orbit
442 * and attitude propagation but should be sufficient for small time shifts
443 * or coarse accuracy.
444 * </p>
445 * <p>
446 * As a rough order of magnitude, the following table shows the extrapolation
447 * errors obtained between this simple shift method and an {@link
448 * NumericalPropagator numerical
449 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
450 * Sun and Moon third bodies attractions, drag and solar radiation pressure.
451 * Beware that these results will be different for other orbits.
452 * </p>
453 * <table border="1">
454 * <caption>Extrapolation Error</caption>
455 * <tr style="background-color: #ccccff"><th>interpolation time (s)</th>
456 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
457 * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td> 18</td><td> 1.1</td></tr>
458 * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td> 72</td><td> 9.1</td></tr>
459 * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
460 * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
461 * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
462 * </table>
463 * @param dt time shift in seconds
464 * @return a new state, shifted with respect to the instance (which is immutable)
465 * except for the mass and additional states which stay unchanged
466 */
467 @Override
468 public SpacecraftState shiftedBy(final double dt) {
469 if (isOrbitDefined()) {
470 return new SpacecraftState(orbit.shiftedBy(dt), null, attitude.shiftedBy(dt),
471 mass, shiftAdditional(dt), new DoubleArrayDictionary(additionalDot), false);
472 } else {
473 return new SpacecraftState(null, absPva.shiftedBy(dt), attitude.shiftedBy(dt),
474 mass, shiftAdditional(dt), new DoubleArrayDictionary(additionalDot), false);
475 }
476 }
477
478 /** Get a time-shifted state.
479 * <p>
480 * The state can be slightly shifted to close dates. This shift is based on
481 * simple models. For orbits, the model is a Keplerian one if no derivatives
482 * are available in the orbit, or Keplerian plus quadratic effect of the
483 * non-Keplerian acceleration if derivatives are available. For attitude,
484 * a polynomial model is used. Neither mass nor additional states change.
485 * Shifting is <em>not</em> intended as a replacement for proper orbit
486 * and attitude propagation but should be sufficient for small time shifts
487 * or coarse accuracy.
488 * </p>
489 * <p>
490 * As a rough order of magnitude, the following table shows the extrapolation
491 * errors obtained between this simple shift method and an {@link
492 * NumericalPropagator numerical
493 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
494 * Sun and Moon third bodies attractions, drag and solar radiation pressure.
495 * Beware that these results will be different for other orbits.
496 * </p>
497 * <table border="1">
498 * <caption>Extrapolation Error</caption>
499 * <tr style="background-color: #ccccff"><th>interpolation time (s)</th>
500 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
501 * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td> 18</td><td> 1.1</td></tr>
502 * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td> 72</td><td> 9.1</td></tr>
503 * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
504 * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
505 * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
506 * </table>
507 * @param dt time shift in seconds
508 * @return a new state, shifted with respect to the instance (which is immutable)
509 * except for the mass and additional states which stay unchanged
510 * @since 13.0
511 */
512 @Override
513 public SpacecraftState shiftedBy(final TimeOffset dt) {
514 if (isOrbitDefined()) {
515 return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
516 mass, shiftAdditional(dt.toDouble()), additionalDot);
517 } else {
518 return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
519 mass, shiftAdditional(dt.toDouble()), additionalDot);
520 }
521 }
522
523 /** Shift additional states.
524 * @param dt time shift in seconds
525 * @return shifted additional states
526 * @since 11.1.1
527 */
528 private DataDictionary shiftAdditional(final double dt) {
529
530 // fast handling when there are no derivatives at all
531 if (additionalDot.size() == 0) {
532 return additional;
533 }
534
535 // there are derivatives, we need to take them into account in the additional state
536 final DataDictionary shifted = new DataDictionary(additional);
537 for (final DoubleArrayDictionary.Entry dotEntry : additionalDot.getData()) {
538 final DataDictionary.Entry entry = shifted.getEntry(dotEntry.getKey());
539 if (entry != null) {
540 entry.scaledIncrement(dt, dotEntry);
541 }
542 }
543
544 return shifted;
545
546 }
547
548 /** Get the absolute position-velocity-acceleration.
549 * <p>
550 * A state contains either an {@link AbsolutePVCoordinates absolute
551 * position-velocity-acceleration} or an {@link Orbit orbit}. Which
552 * one is present can be checked using {@link #isOrbitDefined()}.
553 * </p>
554 * @return absolute position-velocity-acceleration
555 * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
556 * which mean the state rather contains an {@link Orbit}
557 * @see #isOrbitDefined()
558 * @see #getOrbit()
559 */
560 public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException {
561 if (isOrbitDefined()) {
562 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
563 }
564 return absPva;
565 }
566
567 /** Get the current orbit.
568 * <p>
569 * A state contains either an {@link AbsolutePVCoordinates absolute
570 * position-velocity-acceleration} or an {@link Orbit orbit}. Which
571 * one is present can be checked using {@link #isOrbitDefined()}.
572 * </p>
573 * @return the orbit
574 * @exception OrekitIllegalStateException if orbit is null,
575 * which means the state rather contains an {@link AbsolutePVCoordinates absolute
576 * position-velocity-acceleration}
577 * @see #isOrbitDefined()
578 * @see #getAbsPVA()
579 */
580 public Orbit getOrbit() throws OrekitIllegalStateException {
581 if (orbit == null) {
582 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
583 }
584 return orbit;
585 }
586
587 /** {@inheritDoc} */
588 @Override
589 public AbsoluteDate getDate() {
590 return (absPva == null) ? orbit.getDate() : absPva.getDate();
591 }
592
593 /** Get the defining frame.
594 * @return the frame in which state is defined
595 */
596 public Frame getFrame() {
597 return isOrbitDefined() ? orbit.getFrame() : absPva.getFrame();
598 }
599
600 /** Check if an additional data is available.
601 * @param name name of the additional data
602 * @return true if the additional data is available
603 * @see #addAdditionalData(String, Object)
604 * @see #getAdditionalState(String)
605 * @see #getAdditionalData(String)
606 * @see #getAdditionalDataValues()
607 */
608 public boolean hasAdditionalData(final String name) {
609 return additional.getEntry(name) != null;
610 }
611
612 /** Check if an additional state derivative is available.
613 * @param name name of the additional state derivative
614 * @return true if the additional state derivative is available
615 * @see #addAdditionalStateDerivative(String, double[])
616 * @see #getAdditionalStateDerivative(String)
617 * @see #getAdditionalStatesDerivatives()
618 * @since 11.1
619 */
620 public boolean hasAdditionalStateDerivative(final String name) {
621 return additionalDot.getEntry(name) != null;
622 }
623
624 /** Check if two instances have the same set of additional states available.
625 * <p>
626 * Only the names and dimensions of the additional states are compared,
627 * not their values.
628 * </p>
629 * @param state state to compare to instance
630 * @exception MathIllegalStateException if an additional state does not have
631 * the same dimension in both states
632 */
633 public void ensureCompatibleAdditionalStates(final SpacecraftState state)
634 throws MathIllegalStateException {
635
636 // check instance additional states is a subset of the other one
637 for (final DataDictionary.Entry entry : additional.getData()) {
638 final Object other = state.additional.get(entry.getKey());
639 if (other == null || !entry.getValue().getClass().equals(other.getClass())) {
640 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
641 entry.getKey());
642 }
643 if (other instanceof double[] && ((double[]) other).length != ((double[]) entry.getValue()).length) {
644 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
645 ((double[]) other).length, ((double[]) entry.getValue()).length);
646 }
647 }
648
649 // check instance additional states derivatives is a subset of the other one
650 for (final DoubleArrayDictionary.Entry entry : additionalDot.getData()) {
651 final double[] other = state.additionalDot.get(entry.getKey());
652 if (other == null) {
653 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
654 entry.getKey());
655 }
656 if (other.length != entry.getValue().length) {
657 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
658 other.length, entry.getValue().length);
659 }
660 }
661
662 if (state.additional.size() > additional.size()) {
663 // the other state has more additional states
664 for (final DataDictionary.Entry entry : state.additional.getData()) {
665 if (additional.getEntry(entry.getKey()) == null) {
666 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
667 entry.getKey());
668 }
669 }
670 }
671
672 if (state.additionalDot.size() > additionalDot.size()) {
673 // the other state has more additional states
674 for (final DoubleArrayDictionary.Entry entry : state.additionalDot.getData()) {
675 if (additionalDot.getEntry(entry.getKey()) == null) {
676 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
677 entry.getKey());
678 }
679 }
680 }
681
682 }
683
684 /**
685 * Get an additional state.
686 *
687 * @param name name of the additional state
688 * @return value of the additional state
689 * @see #hasAdditionalData(String)
690 * @see #getAdditionalDataValues()
691 */
692 public double[] getAdditionalState(final String name) {
693 final Object data = getAdditionalData(name);
694 if (!(data instanceof double[])) {
695 if (data instanceof Double) {
696 return new double[] {(double) data};
697 } else {
698 throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_BAD_TYPE, name);
699 }
700 }
701 return (double[]) data;
702 }
703
704 /**
705 * Get an additional data.
706 *
707 * @param name name of the additional state
708 * @return value of the additional state
709 * @see #addAdditionalData(String, Object)
710 * @see #hasAdditionalData(String)
711 * @see #getAdditionalDataValues()
712 * @since 13.0
713 */
714 public Object getAdditionalData(final String name) {
715 final Object value = additional.get(name);
716 if (value == null) {
717 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
718 }
719 return value;
720 }
721
722 /** Get an additional state derivative.
723 * @param name name of the additional state derivative
724 * @return value of the additional state derivative
725 * @see #addAdditionalStateDerivative(String, double[])
726 * @see #hasAdditionalStateDerivative(String)
727 * @see #getAdditionalStatesDerivatives()
728 * @since 11.1
729 */
730 public double[] getAdditionalStateDerivative(final String name) {
731 final DoubleArrayDictionary.Entry entry = additionalDot.getEntry(name);
732 if (entry == null) {
733 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
734 }
735 return entry.getValue();
736 }
737
738 /** Get an unmodifiable map of additional data.
739 * @return unmodifiable map of additional data
740 * @see #addAdditionalData(String, Object)
741 * @see #hasAdditionalData(String)
742 * @see #getAdditionalState(String)
743 * @since 11.1
744 */
745 public DataDictionary getAdditionalDataValues() {
746 return additional;
747 }
748
749 /** Get an unmodifiable map of additional states derivatives.
750 * @return unmodifiable map of additional states derivatives
751 * @see #addAdditionalStateDerivative(String, double[])
752 * @see #hasAdditionalStateDerivative(String)
753 * @see #getAdditionalStateDerivative(String)
754 * @since 11.1
755 */
756 public DoubleArrayDictionary getAdditionalStatesDerivatives() {
757 return additionalDot;
758 }
759
760 /** Compute the transform from state defining frame to spacecraft frame.
761 * <p>The spacecraft frame origin is at the point defined by the orbit
762 * (or absolute position-velocity-acceleration), and its orientation is
763 * defined by the attitude.</p>
764 * @return transform from specified frame to current spacecraft frame
765 */
766 public Transform toTransform() {
767 final TimeStampedPVCoordinates pv = getPVCoordinates();
768 return new Transform(pv.getDate(), pv.negate(), attitude.getOrientation());
769 }
770
771 /** Compute the static transform from state defining frame to spacecraft frame.
772 * @return static transform from specified frame to current spacecraft frame
773 * @see #toTransform()
774 * @since 12.0
775 */
776 public StaticTransform toStaticTransform() {
777 return StaticTransform.of(getDate(), getPosition().negate(), attitude.getRotation());
778 }
779
780 /** Get the position in state definition frame.
781 * @return position in state definition frame
782 * @since 12.0
783 * @see #getPVCoordinates()
784 */
785 public Vector3D getPosition() {
786 return isOrbitDefined() ? orbit.getPosition() : absPva.getPosition();
787 }
788
789 /** Get the velocity in state definition frame.
790 * @return velocity in state definition frame
791 * @since 13.1
792 * @see #getPVCoordinates()
793 */
794 public Vector3D getVelocity() {
795 return isOrbitDefined() ? orbit.getVelocity() : absPva.getVelocity();
796 }
797
798 /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
799 * <p>
800 * Compute the position and velocity of the satellite. This method caches its
801 * results, and recompute them only when the method is called with a new value
802 * for mu. The result is provided as a reference to the internally cached
803 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
804 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
805 * </p>
806 * @return pvCoordinates in orbit definition frame
807 */
808 public TimeStampedPVCoordinates getPVCoordinates() {
809 return isOrbitDefined() ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
810 }
811
812 /** Get the position in given output frame.
813 * @param outputFrame frame in which position should be defined
814 * @return position in given output frame
815 * @since 12.0
816 * @see #getPVCoordinates(Frame)
817 */
818 public Vector3D getPosition(final Frame outputFrame) {
819 return isOrbitDefined() ? orbit.getPosition(outputFrame) : absPva.getPosition(outputFrame);
820 }
821
822 /** Get the {@link TimeStampedPVCoordinates} in given output frame.
823 * <p>
824 * Compute the position and velocity of the satellite. This method caches its
825 * results, and recompute them only when the method is called with a new value
826 * for mu. The result is provided as a reference to the internally cached
827 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
828 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
829 * </p>
830 * @param outputFrame frame in which coordinates should be defined
831 * @return pvCoordinates in given output frame
832 */
833 public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
834 return isOrbitDefined() ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
835 }
836
837 /** Get the attitude.
838 * @return the attitude.
839 */
840 public Attitude getAttitude() {
841 return attitude;
842 }
843
844 /** Gets the current mass.
845 * @return the mass (kg)
846 */
847 public double getMass() {
848 return mass;
849 }
850
851 @Override
852 public String toString() {
853 return "SpacecraftState{" +
854 "orbit=" + orbit +
855 ", attitude=" + attitude +
856 ", mass=" + mass +
857 ", additional=" + additional +
858 ", additionalDot=" + additionalDot +
859 '}';
860 }
861 }