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