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