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