1 /* Copyright 2002-2016 CS Systèmes d'Information
2 * Licensed to CS Systèmes d'Information (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.propagation;
18
19 import java.io.Serializable;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26
27 import org.apache.commons.math3.analysis.interpolation.HermiteInterpolator;
28 import org.apache.commons.math3.exception.DimensionMismatchException;
29 import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
30 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
31 import org.apache.commons.math3.util.FastMath;
32 import org.orekit.attitudes.Attitude;
33 import org.orekit.attitudes.LofOffset;
34 import org.orekit.errors.OrekitIllegalArgumentException;
35 import org.orekit.errors.OrekitException;
36 import org.orekit.errors.OrekitMessages;
37 import org.orekit.frames.Frame;
38 import org.orekit.frames.LOFType;
39 import org.orekit.frames.Transform;
40 import org.orekit.orbits.Orbit;
41 import org.orekit.time.AbsoluteDate;
42 import org.orekit.time.TimeInterpolable;
43 import org.orekit.time.TimeShiftable;
44 import org.orekit.time.TimeStamped;
45 import org.orekit.utils.TimeStampedAngularCoordinates;
46 import org.orekit.utils.TimeStampedPVCoordinates;
47
48
49 /** This class is the representation of a complete state holding orbit, attitude
50 * and mass information at a given date.
51 *
52 * <p>It contains an {@link Orbit orbital state} at a current
53 * {@link AbsoluteDate} both handled by an {@link Orbit}, plus the current
54 * mass and attitude. Orbit and state are guaranteed to be consistent in terms
55 * of date and reference frame. The spacecraft state may also contain additional
56 * states, which are simply named double arrays which can hold any user-defined
57 * data.
58 * </p>
59 * <p>
60 * The state can be slightly shifted to close dates. This shift is based on
61 * a simple keplerian model for orbit, a linear extrapolation for attitude
62 * taking the spin rate into account and no mass change. It is <em>not</em>
63 * intended as a replacement for proper orbit and attitude propagation but
64 * should be sufficient for either small time shifts or coarse accuracy.
65 * </p>
66 * <p>
67 * The instance <code>SpacecraftState</code> is guaranteed to be immutable.
68 * </p>
69 * @see org.orekit.propagation.numerical.NumericalPropagator
70 * @author Fabien Maussion
71 * @author Véronique Pommier-Maurussane
72 * @author Luc Maisonobe
73 */
74 public class SpacecraftState
75 implements TimeStamped, TimeShiftable<SpacecraftState>, TimeInterpolable<SpacecraftState>, Serializable {
76
77 /** Serializable UID. */
78 private static final long serialVersionUID = 20130407L;
79
80 /** Default mass. */
81 private static final double DEFAULT_MASS = 1000.0;
82
83 /**
84 * tolerance on date comparison in {@link #checkConsistency(Orbit, Attitude)}. 100 ns
85 * corresponds to sub-mm accuracy at LEO orbital velocities.
86 */
87 private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;
88
89 /** Orbital state. */
90 private final Orbit orbit;
91
92 /** Attitude. */
93 private final Attitude attitude;
94
95 /** Current mass (kg). */
96 private final double mass;
97
98 /** Additional states. */
99 private final Map<String, double[]> additional;
100
101 /** Build a spacecraft state from orbit only.
102 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
103 * @param orbit the orbit
104 * @exception OrekitException if default attitude cannot be computed
105 */
106 public SpacecraftState(final Orbit orbit)
107 throws OrekitException {
108 this(orbit,
109 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
110 DEFAULT_MASS, null);
111 }
112
113 /** Build a spacecraft state from orbit and attitude provider.
114 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
115 * @param orbit the orbit
116 * @param attitude attitude
117 * @exception IllegalArgumentException if orbit and attitude dates
118 * or frames are not equal
119 */
120 public SpacecraftState(final Orbit orbit, final Attitude attitude)
121 throws IllegalArgumentException {
122 this(orbit, attitude, DEFAULT_MASS, null);
123 }
124
125 /** Create a new instance from orbit and mass.
126 * <p>Attitude law is set to an unspecified default attitude.</p>
127 * @param orbit the orbit
128 * @param mass the mass (kg)
129 * @exception OrekitException if default attitude cannot be computed
130 */
131 public SpacecraftState(final Orbit orbit, final double mass)
132 throws OrekitException {
133 this(orbit,
134 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
135 mass, null);
136 }
137
138 /** Build a spacecraft state from orbit, attitude provider and mass.
139 * @param orbit the orbit
140 * @param attitude attitude
141 * @param mass the mass (kg)
142 * @exception IllegalArgumentException if orbit and attitude dates
143 * or frames are not equal
144 */
145 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass)
146 throws IllegalArgumentException {
147 this(orbit, attitude, mass, null);
148 }
149
150 /** Build a spacecraft state from orbit only.
151 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
152 * @param orbit the orbit
153 * @param additional additional states
154 * @exception OrekitException if default attitude cannot be computed
155 */
156 public SpacecraftState(final Orbit orbit, final Map<String, double[]> additional)
157 throws OrekitException {
158 this(orbit,
159 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
160 DEFAULT_MASS, additional);
161 }
162
163 /** Build a spacecraft state from orbit and attitude provider.
164 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
165 * @param orbit the orbit
166 * @param attitude attitude
167 * @param additional additional states
168 * @exception IllegalArgumentException if orbit and attitude dates
169 * or frames are not equal
170 */
171 public SpacecraftState(final Orbit orbit, final Attitude attitude, final Map<String, double[]> additional)
172 throws IllegalArgumentException {
173 this(orbit, attitude, DEFAULT_MASS, additional);
174 }
175
176 /** Create a new instance from orbit and mass.
177 * <p>Attitude law is set to an unspecified default attitude.</p>
178 * @param orbit the orbit
179 * @param mass the mass (kg)
180 * @param additional additional states
181 * @exception OrekitException if default attitude cannot be computed
182 */
183 public SpacecraftState(final Orbit orbit, final double mass, final Map<String, double[]> additional)
184 throws OrekitException {
185 this(orbit,
186 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
187 mass, additional);
188 }
189
190 /** Build a spacecraft state from orbit, attitude provider and mass.
191 * @param orbit the orbit
192 * @param attitude attitude
193 * @param mass the mass (kg)
194 * @param additional additional states (may be null if no additional states are available)
195 * @exception IllegalArgumentException if orbit and attitude dates
196 * or frames are not equal
197 */
198 public SpacecraftState(final Orbit orbit, final Attitude attitude,
199 final double mass, final Map<String, double[]> additional)
200 throws IllegalArgumentException {
201 checkConsistency(orbit, attitude);
202 this.orbit = orbit;
203 this.attitude = attitude;
204 this.mass = mass;
205 if (additional == null) {
206 this.additional = Collections.emptyMap();
207 } else {
208 this.additional = new HashMap<String, double[]>(additional.size());
209 for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
210 this.additional.put(entry.getKey(), entry.getValue().clone());
211 }
212 }
213 }
214
215 /** Add an additional state.
216 * <p>
217 * {@link SpacecraftState SpacecraftState} instances are immutable,
218 * so this method does <em>not</em> change the instance, but rather
219 * creates a new instance, which has the same orbit, attitude, mass
220 * and additional states as the original instance, except it also
221 * has the specified state. If the original instance already had an
222 * additional state with the same name, it will be overridden. If it
223 * did not have any additional state with that name, the new instance
224 * will have one more additional state than the original instance.
225 * </p>
226 * @param name name of the additional state
227 * @param value value of the additional state
228 * @return a new instance, with the additional state added
229 * @see #hasAdditionalState(String)
230 * @see #getAdditionalState(String)
231 * @see #getAdditionalStates()
232 */
233 public SpacecraftState addAdditionalState(final String name, final double ... value) {
234 final Map<String, double[]> newMap = new HashMap<String, double[]>(additional.size() + 1);
235 newMap.putAll(additional);
236 newMap.put(name, value.clone());
237 return new SpacecraftState(orbit, attitude, mass, newMap);
238 }
239
240 /** Check orbit and attitude dates are equal.
241 * @param orbit the orbit
242 * @param attitude attitude
243 * @exception IllegalArgumentException if orbit and attitude dates
244 * are not equal
245 */
246 private static void checkConsistency(final Orbit orbit, final Attitude attitude)
247 throws IllegalArgumentException {
248 if (FastMath.abs(orbit.getDate().durationFrom(attitude.getDate())) >
249 DATE_INCONSISTENCY_THRESHOLD) {
250 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
251 orbit.getDate(), attitude.getDate());
252 }
253 if (orbit.getFrame() != attitude.getReferenceFrame()) {
254 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
255 orbit.getFrame().getName(),
256 attitude.getReferenceFrame().getName());
257 }
258 }
259
260 /** Get a time-shifted state.
261 * <p>
262 * The state can be slightly shifted to close dates. This shift is based on
263 * a simple keplerian model for orbit, a linear extrapolation for attitude
264 * taking the spin rate into account and neither mass nor additional states
265 * changes. It is <em>not</em> intended as a replacement for proper orbit
266 * and attitude propagation but should be sufficient for small time shifts
267 * or coarse accuracy.
268 * </p>
269 * <p>
270 * As a rough order of magnitude, the following table shows the interpolation
271 * errors obtained between this simple shift method and an {@link
272 * org.orekit.propagation.analytical.EcksteinHechlerPropagator Eckstein-Heschler
273 * propagator} for an 800km altitude nearly circular polar Earth orbit with
274 * {@link org.orekit.attitudes.BodyCenterPointing body center pointing}. Beware
275 * that these results may be different for other orbits.
276 * </p>
277 * <table border="1" cellpadding="5">
278 * <tr bgcolor="#ccccff"><th>interpolation time (s)</th>
279 * <th>position error (m)</th><th>velocity error (m/s)</th>
280 * <th>attitude error (°)</th></tr>
281 * <tr><td bgcolor="#eeeeff"> 60</td><td> 20</td><td>1</td><td>0.001</td></tr>
282 * <tr><td bgcolor="#eeeeff">120</td><td> 100</td><td>2</td><td>0.002</td></tr>
283 * <tr><td bgcolor="#eeeeff">300</td><td> 600</td><td>4</td><td>0.005</td></tr>
284 * <tr><td bgcolor="#eeeeff">600</td><td>2000</td><td>6</td><td>0.008</td></tr>
285 * <tr><td bgcolor="#eeeeff">900</td><td>4000</td><td>6</td><td>0.010</td></tr>
286 * </table>
287 * @param dt time shift in seconds
288 * @return a new state, shifted with respect to the instance (which is immutable)
289 * except for the mass which stay unchanged
290 */
291 public SpacecraftState shiftedBy(final double dt) {
292 return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
293 mass, additional);
294 }
295
296 /** {@inheritDoc}
297 * <p>
298 * The additional states that are interpolated are the ones already present
299 * in the instance. The sample instances must therefore have at least the same
300 * additional states has the instance. They may have more additional states,
301 * but the extra ones will be ignored.
302 * </p>
303 * <p>
304 * As this implementation of interpolation is polynomial, it should be used only
305 * with small samples (about 10-20 points) in order to avoid <a
306 * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
307 * and numerical problems (including NaN appearing).
308 * </p>
309 */
310 public SpacecraftState interpolate(final AbsoluteDate date,
311 final Collection<SpacecraftState> sample)
312 throws OrekitException {
313
314 // prepare interpolators
315 final List<Orbit> orbits = new ArrayList<Orbit>(sample.size());
316 final List<Attitude> attitudes = new ArrayList<Attitude>(sample.size());
317 final HermiteInterpolator massInterpolator = new HermiteInterpolator();
318 final Map<String, HermiteInterpolator> additionalInterpolators =
319 new HashMap<String, HermiteInterpolator>(additional.size());
320 for (final String name : additional.keySet()) {
321 additionalInterpolators.put(name, new HermiteInterpolator());
322 }
323
324 // extract sample data
325 for (final SpacecraftState state : sample) {
326 final double deltaT = state.getDate().durationFrom(date);
327 orbits.add(state.getOrbit());
328 attitudes.add(state.getAttitude());
329 massInterpolator.addSamplePoint(deltaT,
330 new double[] {
331 state.getMass()
332 });
333 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
334 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
335 }
336 }
337
338 // perform interpolations
339 final Orbit interpolatedOrbit = orbit.interpolate(date, orbits);
340 final Attitude interpolatedAttitude = attitude.interpolate(date, attitudes);
341 final double interpolatedMass = massInterpolator.value(0)[0];
342 final Map<String, double[]> interpolatedAdditional;
343 if (additional.isEmpty()) {
344 interpolatedAdditional = null;
345 } else {
346 interpolatedAdditional = new HashMap<String, double[]>(additional.size());
347 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
348 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(0));
349 }
350 }
351
352 // create the complete interpolated state
353 return new SpacecraftState(interpolatedOrbit, interpolatedAttitude,
354 interpolatedMass, interpolatedAdditional);
355
356 }
357
358 /** Gets the current orbit.
359 * @return the orbit
360 */
361 public Orbit getOrbit() {
362 return orbit;
363 }
364
365 /** Get the date.
366 * @return date
367 */
368 public AbsoluteDate getDate() {
369 return orbit.getDate();
370 }
371
372 /** Get the inertial frame.
373 * @return the frame
374 */
375 public Frame getFrame() {
376 return orbit.getFrame();
377 }
378
379 /** Check if an additional state is available.
380 * @param name name of the additional state
381 * @return true if the additional state is available
382 * @see #addAdditionalState(String, double[])
383 * @see #getAdditionalState(String)
384 * @see #getAdditionalStates()
385 */
386 public boolean hasAdditionalState(final String name) {
387 return additional.containsKey(name);
388 }
389
390 /** Check if two instances have the same set of additional states available.
391 * <p>
392 * Only the names and dimensions of the additional states are compared,
393 * not their values.
394 * </p>
395 * @param state state to compare to instance
396 * @exception OrekitException if either instance or state supports an additional
397 * state not supported by the other one
398 * @exception DimensionMismatchException if an additional state does not have
399 * the same dimension in both states
400 */
401 public void ensureCompatibleAdditionalStates(final SpacecraftState state)
402 throws OrekitException, DimensionMismatchException {
403
404 // check instance additional states is a subset of the other one
405 for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
406 final double[] other = state.additional.get(entry.getKey());
407 if (other == null) {
408 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
409 entry.getKey());
410 }
411 if (other.length != entry.getValue().length) {
412 throw new DimensionMismatchException(other.length, entry.getValue().length);
413 }
414 }
415
416 if (state.additional.size() > additional.size()) {
417 // the other state has more additional states
418 for (final String name : state.additional.keySet()) {
419 if (!additional.containsKey(name)) {
420 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
421 name);
422 }
423 }
424 }
425
426 }
427
428 /** Get an additional state.
429 * @param name name of the additional state
430 * @return value of the additional state
431 * @exception OrekitException if no additional state with that name exists
432 * @see #addAdditionalState(String, double[])
433 * @see #hasAdditionalState(String)
434 * @see #getAdditionalStates()
435 */
436 public double[] getAdditionalState(final String name) throws OrekitException {
437 if (!additional.containsKey(name)) {
438 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
439 }
440 return additional.get(name).clone();
441 }
442
443 /** Get an unmodifiable map of additional states.
444 * @return unmodifiable map of additional states
445 * @see #addAdditionalState(String, double[])
446 * @see #hasAdditionalState(String)
447 * @see #getAdditionalState(String)
448 */
449 public Map<String, double[]> getAdditionalStates() {
450 return Collections.unmodifiableMap(additional);
451 }
452
453 /** Compute the transform from orbite/attitude reference frame to spacecraft frame.
454 * <p>The spacecraft frame origin is at the point defined by the orbit,
455 * and its orientation is defined by the attitude.</p>
456 * @return transform from specified frame to current spacecraft frame
457 */
458 public Transform toTransform() {
459 final AbsoluteDate date = orbit.getDate();
460 return new Transform(date,
461 new Transform(date, orbit.getPVCoordinates().negate()),
462 new Transform(date, attitude.getOrientation()));
463 }
464
465 /** Get the central attraction coefficient.
466 * @return mu central attraction coefficient (m^3/s^2)
467 */
468 public double getMu() {
469 return orbit.getMu();
470 }
471
472 /** Get the keplerian period.
473 * <p>The keplerian period is computed directly from semi major axis
474 * and central acceleration constant.</p>
475 * @return keplerian period in seconds
476 */
477 public double getKeplerianPeriod() {
478 return orbit.getKeplerianPeriod();
479 }
480
481 /** Get the keplerian mean motion.
482 * <p>The keplerian mean motion is computed directly from semi major axis
483 * and central acceleration constant.</p>
484 * @return keplerian mean motion in radians per second
485 */
486 public double getKeplerianMeanMotion() {
487 return orbit.getKeplerianMeanMotion();
488 }
489
490 /** Get the semi-major axis.
491 * @return semi-major axis (m)
492 */
493 public double getA() {
494 return orbit.getA();
495 }
496
497 /** Get the first component of the eccentricity vector (as per equinoctial parameters).
498 * @return e cos(ω + Ω), first component of eccentricity vector
499 * @see #getE()
500 */
501 public double getEquinoctialEx() {
502 return orbit.getEquinoctialEx();
503 }
504
505 /** Get the second component of the eccentricity vector (as per equinoctial parameters).
506 * @return e sin(ω + Ω), second component of the eccentricity vector
507 * @see #getE()
508 */
509 public double getEquinoctialEy() {
510 return orbit.getEquinoctialEy();
511 }
512
513 /** Get the first component of the inclination vector (as per equinoctial parameters).
514 * @return tan(i/2) cos(Ω), first component of the inclination vector
515 * @see #getI()
516 */
517 public double getHx() {
518 return orbit.getHx();
519 }
520
521 /** Get the second component of the inclination vector (as per equinoctial parameters).
522 * @return tan(i/2) sin(Ω), second component of the inclination vector
523 * @see #getI()
524 */
525 public double getHy() {
526 return orbit.getHy();
527 }
528
529 /** Get the true latitude argument (as per equinoctial parameters).
530 * @return v + ω + Ω true latitude argument (rad)
531 * @see #getLE()
532 * @see #getLM()
533 */
534 public double getLv() {
535 return orbit.getLv();
536 }
537
538 /** Get the eccentric latitude argument (as per equinoctial parameters).
539 * @return E + ω + Ω eccentric latitude argument (rad)
540 * @see #getLv()
541 * @see #getLM()
542 */
543 public double getLE() {
544 return orbit.getLE();
545 }
546
547 /** Get the mean latitude argument (as per equinoctial parameters).
548 * @return M + ω + Ω mean latitude argument (rad)
549 * @see #getLv()
550 * @see #getLE()
551 */
552 public double getLM() {
553 return orbit.getLM();
554 }
555
556 // Additional orbital elements
557
558 /** Get the eccentricity.
559 * @return eccentricity
560 * @see #getEquinoctialEx()
561 * @see #getEquinoctialEy()
562 */
563 public double getE() {
564 return orbit.getE();
565 }
566
567 /** Get the inclination.
568 * @return inclination (rad)
569 * @see #getHx()
570 * @see #getHy()
571 */
572 public double getI() {
573 return orbit.getI();
574 }
575
576 /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
577 * Compute the position and velocity of the satellite. This method caches its
578 * results, and recompute them only when the method is called with a new value
579 * for mu. The result is provided as a reference to the internally cached
580 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
581 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
582 * @return pvCoordinates in orbit definition frame
583 */
584 public TimeStampedPVCoordinates getPVCoordinates() {
585 return orbit.getPVCoordinates();
586 }
587
588 /** Get the {@link TimeStampedPVCoordinates} in given output frame.
589 * Compute the position and velocity of the satellite. This method caches its
590 * results, and recompute them only when the method is called with a new value
591 * for mu. The result is provided as a reference to the internally cached
592 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
593 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
594 * @param outputFrame frame in which coordinates should be defined
595 * @return pvCoordinates in orbit definition frame
596 * @exception OrekitException if the transformation between frames cannot be computed
597 */
598 public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame)
599 throws OrekitException {
600 return orbit.getPVCoordinates(outputFrame);
601 }
602
603 /** Get the attitude.
604 * @return the attitude.
605 */
606 public Attitude getAttitude() {
607 return attitude;
608 }
609
610 /** Gets the current mass.
611 * @return the mass (kg)
612 */
613 public double getMass() {
614 return mass;
615 }
616
617 /** Replace the instance with a data transfer object for serialization.
618 * @return data transfer object that will be serialized
619 */
620 private Object writeReplace() {
621 return new DTO(this);
622 }
623
624 /** Internal class used only for serialization. */
625 private static class DTO implements Serializable {
626
627 /** Serializable UID. */
628 private static final long serialVersionUID = 20140617L;
629
630 /** Orbit. */
631 private final Orbit orbit;
632
633 /** Attitude and mass double values. */
634 private double[] d;
635
636 /** Additional states. */
637 private final Map<String, double[]> additional;
638
639 /** Simple constructor.
640 * @param state instance to serialize
641 */
642 private DTO(final SpacecraftState state) {
643
644 this.orbit = state.orbit;
645 this.additional = state.additional.isEmpty() ? null : state.additional;
646
647 final Rotation rotation = state.attitude.getRotation();
648 final Vector3D spin = state.attitude.getSpin();
649 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
650 this.d = new double[] {
651 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
652 spin.getX(), spin.getY(), spin.getZ(),
653 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
654 state.mass
655 };
656
657 }
658
659 /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
660 * @return replacement {@link SpacecraftState}
661 */
662 private Object readResolve() {
663 return new SpacecraftState(orbit,
664 new Attitude(orbit.getFrame(),
665 new TimeStampedAngularCoordinates(orbit.getDate(),
666 new Rotation(d[0], d[1], d[2], d[3], false),
667 new Vector3D(d[4], d[5], d[6]),
668 new Vector3D(d[7], d[8], d[9]))),
669 d[10], additional);
670 }
671
672 }
673
674 }