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