1 /* Copyright 2002-2020 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 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.OrekitIllegalArgumentException;
37 import org.orekit.errors.OrekitIllegalStateException;
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.AbsolutePVCoordinates;
48 import org.orekit.utils.TimeStampedAngularCoordinates;
49 import org.orekit.utils.TimeStampedPVCoordinates;
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 /** Trajectory state, when it is not an orbit. */
95 private final AbsolutePVCoordinates absPva;
96
97 /** Attitude. */
98 private final Attitude attitude;
99
100 /** Current mass (kg). */
101 private final double mass;
102
103 /** Additional states. */
104 private final Map<String, double[]> additional;
105
106 /** Build a spacecraft state from orbit only.
107 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
108 * @param orbit the orbit
109 */
110 public SpacecraftState(final Orbit orbit) {
111 this(orbit,
112 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
113 DEFAULT_MASS, null);
114 }
115
116 /** Build a spacecraft state from orbit and attitude provider.
117 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
118 * @param orbit the orbit
119 * @param attitude attitude
120 * @exception IllegalArgumentException if orbit and attitude dates
121 * or frames are not equal
122 */
123 public SpacecraftState(final Orbit orbit, final Attitude attitude)
124 throws IllegalArgumentException {
125 this(orbit, attitude, DEFAULT_MASS, null);
126 }
127
128 /** Create a new instance from orbit and mass.
129 * <p>Attitude law is set to an unspecified default attitude.</p>
130 * @param orbit the orbit
131 * @param mass the mass (kg)
132 */
133 public SpacecraftState(final Orbit orbit, final double mass) {
134 this(orbit,
135 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
136 mass, null);
137 }
138
139 /** Build a spacecraft state from orbit, attitude provider and mass.
140 * @param orbit the orbit
141 * @param attitude attitude
142 * @param mass the mass (kg)
143 * @exception IllegalArgumentException if orbit and attitude dates
144 * or frames are not equal
145 */
146 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass)
147 throws IllegalArgumentException {
148 this(orbit, attitude, mass, null);
149 }
150
151 /** Build a spacecraft state from orbit only.
152 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
153 * @param orbit the orbit
154 * @param additional additional states
155 */
156 public SpacecraftState(final Orbit orbit, final Map<String, double[]> additional) {
157 this(orbit,
158 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
159 DEFAULT_MASS, additional);
160 }
161
162 /** Build a spacecraft state from orbit and attitude provider.
163 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
164 * @param orbit the orbit
165 * @param attitude attitude
166 * @param additional additional states
167 * @exception IllegalArgumentException if orbit and attitude dates
168 * or frames are not equal
169 */
170 public SpacecraftState(final Orbit orbit, final Attitude attitude, final Map<String, double[]> additional)
171 throws IllegalArgumentException {
172 this(orbit, attitude, DEFAULT_MASS, additional);
173 }
174
175 /** Create a new instance from orbit and mass.
176 * <p>Attitude law is set to an unspecified default attitude.</p>
177 * @param orbit the orbit
178 * @param mass the mass (kg)
179 * @param additional additional states
180 */
181 public SpacecraftState(final Orbit orbit, final double mass, final Map<String, double[]> additional) {
182 this(orbit,
183 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
184 mass, additional);
185 }
186
187 /** Build a spacecraft state from orbit, attitude provider and mass.
188 * @param orbit the orbit
189 * @param attitude attitude
190 * @param mass the mass (kg)
191 * @param additional additional states (may be null if no additional states are available)
192 * @exception IllegalArgumentException if orbit and attitude dates
193 * or frames are not equal
194 */
195 public SpacecraftState(final Orbit orbit, final Attitude attitude,
196 final double mass, final Map<String, double[]> additional)
197 throws IllegalArgumentException {
198 checkConsistency(orbit, attitude);
199 this.orbit = orbit;
200 this.absPva = null;
201 this.attitude = attitude;
202 this.mass = mass;
203 if (additional == null) {
204 this.additional = Collections.emptyMap();
205 } else {
206 this.additional = new HashMap<String, double[]>(additional.size());
207 for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
208 this.additional.put(entry.getKey(), entry.getValue().clone());
209 }
210 }
211 }
212
213
214
215 /** Build a spacecraft state from orbit only.
216 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
217 * @param absPva position-velocity-acceleration
218 */
219 public SpacecraftState(final AbsolutePVCoordinates absPva) {
220 this(absPva,
221 new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
222 DEFAULT_MASS, null);
223 }
224
225 /** Build a spacecraft state from orbit and attitude provider.
226 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
227 * @param absPva position-velocity-acceleration
228 * @param attitude attitude
229 * @exception IllegalArgumentException if orbit and attitude dates
230 * or frames are not equal
231 */
232 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude)
233 throws IllegalArgumentException {
234 this(absPva, attitude, DEFAULT_MASS, null);
235 }
236
237 /** Create a new instance from orbit and mass.
238 * <p>Attitude law is set to an unspecified default attitude.</p>
239 * @param absPva position-velocity-acceleration
240 * @param mass the mass (kg)
241 */
242 public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) {
243 this(absPva,
244 new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
245 mass, null);
246 }
247
248 /** Build a spacecraft state from orbit, attitude provider and mass.
249 * @param absPva position-velocity-acceleration
250 * @param attitude attitude
251 * @param mass the mass (kg)
252 * @exception IllegalArgumentException if orbit and attitude dates
253 * or frames are not equal
254 */
255 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass)
256 throws IllegalArgumentException {
257 this(absPva, attitude, mass, null);
258 }
259
260 /** Build a spacecraft state from orbit only.
261 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
262 * @param absPva position-velocity-acceleration
263 * @param additional additional states
264 */
265 public SpacecraftState(final AbsolutePVCoordinates absPva, final Map<String, double[]> additional) {
266 this(absPva,
267 new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
268 DEFAULT_MASS, additional);
269 }
270
271 /** Build a spacecraft state from orbit and attitude provider.
272 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
273 * @param absPva position-velocity-acceleration
274 * @param attitude attitude
275 * @param additional additional states
276 * @exception IllegalArgumentException if orbit and attitude dates
277 * or frames are not equal
278 */
279 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final Map<String, double[]> additional)
280 throws IllegalArgumentException {
281 this(absPva, attitude, DEFAULT_MASS, additional);
282 }
283
284 /** Create a new instance from orbit and mass.
285 * <p>Attitude law is set to an unspecified default attitude.</p>
286 * @param absPva position-velocity-acceleration
287 * @param mass the mass (kg)
288 * @param additional additional states
289 */
290 public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass, final Map<String, double[]> additional) {
291 this(absPva,
292 new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
293 mass, additional);
294 }
295
296 /** Build a spacecraft state from orbit, attitude provider and mass.
297 * @param absPva position-velocity-acceleration
298 * @param attitude attitude
299 * @param mass the mass (kg)
300 * @param additional additional states (may be null if no additional states are available)
301 * @exception IllegalArgumentException if orbit and attitude dates
302 * or frames are not equal
303 */
304 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude,
305 final double mass, final Map<String, double[]> additional)
306 throws IllegalArgumentException {
307 checkConsistency(absPva, attitude);
308 this.orbit = null;
309 this.absPva = absPva;
310 this.attitude = attitude;
311 this.mass = mass;
312 if (additional == null) {
313 this.additional = Collections.emptyMap();
314 } else {
315 this.additional = new HashMap<String, double[]>(additional.size());
316 for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
317 this.additional.put(entry.getKey(), entry.getValue().clone());
318 }
319 }
320 }
321
322 /** Add an additional state.
323 * <p>
324 * {@link SpacecraftState SpacecraftState} instances are immutable,
325 * so this method does <em>not</em> change the instance, but rather
326 * creates a new instance, which has the same orbit, attitude, mass
327 * and additional states as the original instance, except it also
328 * has the specified state. If the original instance already had an
329 * additional state with the same name, it will be overridden. If it
330 * did not have any additional state with that name, the new instance
331 * will have one more additional state than the original instance.
332 * </p>
333 * @param name name of the additional state
334 * @param value value of the additional state
335 * @return a new instance, with the additional state added
336 * @see #hasAdditionalState(String)
337 * @see #getAdditionalState(String)
338 * @see #getAdditionalStates()
339 */
340 public SpacecraftState addAdditionalState(final String name, final double... value) {
341 final Map<String, double[]> newMap = new HashMap<String, double[]>(additional.size() + 1);
342 newMap.putAll(additional);
343 newMap.put(name, value.clone());
344 if (absPva == null) {
345 return new SpacecraftState(orbit, attitude, mass, newMap);
346 } else {
347 return new SpacecraftState(absPva, attitude, mass, newMap);
348 }
349 }
350
351 /** Check orbit and attitude dates are equal.
352 * @param orbit the orbit
353 * @param attitude attitude
354 * @exception IllegalArgumentException if orbit and attitude dates
355 * are not equal
356 */
357 private static void checkConsistency(final Orbit orbit, final Attitude attitude)
358 throws IllegalArgumentException {
359 if (FastMath.abs(orbit.getDate().durationFrom(attitude.getDate())) >
360 DATE_INCONSISTENCY_THRESHOLD) {
361 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
362 orbit.getDate(), attitude.getDate());
363 }
364 if (orbit.getFrame() != attitude.getReferenceFrame()) {
365 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
366 orbit.getFrame().getName(),
367 attitude.getReferenceFrame().getName());
368 }
369 }
370
371 /** Check if the state contains an orbit part.
372 * <p>
373 * A state contains either an {@link AbsolutePVCoordinates absolute
374 * position-velocity-acceleration} or an {@link Orbit orbit}.
375 * </p>
376 * @return true if state contains an orbit (in which case {@link #getOrbit()}
377 * will not throw an exception), or false if the state contains an
378 * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
379 * will not throw an exception)
380 */
381 public boolean isOrbitDefined() {
382 return orbit != null;
383 }
384
385 /** Check AbsolutePVCoordinates and attitude dates are equal.
386 * @param absPva position-velocity-acceleration
387 * @param attitude attitude
388 * @exception IllegalArgumentException if orbit and attitude dates
389 * are not equal
390 */
391 private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude)
392 throws IllegalArgumentException {
393 if (FastMath.abs(absPva.getDate().durationFrom(attitude.getDate())) >
394 DATE_INCONSISTENCY_THRESHOLD) {
395 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
396 absPva.getDate(), attitude.getDate());
397 }
398 if (absPva.getFrame() != attitude.getReferenceFrame()) {
399 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
400 absPva.getFrame().getName(),
401 attitude.getReferenceFrame().getName());
402 }
403 }
404
405 /** Get a time-shifted state.
406 * <p>
407 * The state can be slightly shifted to close dates. This shift is based on
408 * simple models. For orbits, the model is a Keplerian one if no derivatives
409 * are available in the orbit, or Keplerian plus quadratic effect of the
410 * non-Keplerian acceleration if derivatives are available. For attitude,
411 * a polynomial model is used. Neither mass nor additional states change.
412 * Shifting is <em>not</em> intended as a replacement for proper orbit
413 * and attitude propagation but should be sufficient for small time shifts
414 * or coarse accuracy.
415 * </p>
416 * <p>
417 * As a rough order of magnitude, the following table shows the extrapolation
418 * errors obtained between this simple shift method and an {@link
419 * org.orekit.propagation.numerical.NumericalPropagator numerical
420 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
421 * Sun and Moon third bodies attractions, drag and solar radiation pressure.
422 * Beware that these results will be different for other orbits.
423 * </p>
424 * <table border="1" cellpadding="5">
425 * <caption>Extrapolation Error</caption>
426 * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
427 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
428 * <tr><td style="background-color: #eeeeff;"> 60</td><td> 18</td><td> 1.1</td></tr>
429 * <tr><td style="background-color: #eeeeff;">120</td><td> 72</td><td> 9.1</td></tr>
430 * <tr><td style="background-color: #eeeeff;">300</td><td> 447</td><td> 140</td></tr>
431 * <tr><td style="background-color: #eeeeff;">600</td><td>1601</td><td>1067</td></tr>
432 * <tr><td style="background-color: #eeeeff;">900</td><td>3141</td><td>3307</td></tr>
433 * </table>
434 * @param dt time shift in seconds
435 * @return a new state, shifted with respect to the instance (which is immutable)
436 * except for the mass and additional states which stay unchanged
437 */
438 public SpacecraftState shiftedBy(final double dt) {
439 if (absPva == null) {
440 return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
441 mass, additional);
442 } else {
443 return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
444 mass, additional);
445 }
446 }
447
448 /** {@inheritDoc}
449 * <p>
450 * The additional states that are interpolated are the ones already present
451 * in the instance. The sample instances must therefore have at least the same
452 * additional states has the instance. They may have more additional states,
453 * but the extra ones will be ignored.
454 * </p>
455 * <p>
456 * The instance and all the sample instances <em>must</em> be based on similar
457 * trajectory data, i.e. they must either all be based on orbits or all be based
458 * on absolute position-velocity-acceleration. Any inconsistency will trigger
459 * an {@link OrekitIllegalStateException}.
460 * </p>
461 * <p>
462 * As this implementation of interpolation is polynomial, it should be used only
463 * with small samples (about 10-20 points) in order to avoid <a
464 * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
465 * and numerical problems (including NaN appearing).
466 * </p>
467 * @exception OrekitIllegalStateException if some instances are not based on
468 * similar trajectory data
469 */
470 public SpacecraftState interpolate(final AbsoluteDate date,
471 final Stream<SpacecraftState> sample) {
472
473 // prepare interpolators
474 final List<Orbit> orbits;
475 final List<AbsolutePVCoordinates> absPvas;
476 if (isOrbitDefined()) {
477 orbits = new ArrayList<Orbit>();
478 absPvas = null;
479 } else {
480 orbits = null;
481 absPvas = new ArrayList<AbsolutePVCoordinates>();
482 }
483 final List<Attitude> attitudes = new ArrayList<Attitude>();
484 final HermiteInterpolator massInterpolator = new HermiteInterpolator();
485 final Map<String, HermiteInterpolator> additionalInterpolators =
486 new HashMap<String, HermiteInterpolator>(additional.size());
487 for (final String name : additional.keySet()) {
488 additionalInterpolators.put(name, new HermiteInterpolator());
489 }
490
491 // extract sample data
492 sample.forEach(state -> {
493 final double deltaT = state.getDate().durationFrom(date);
494 if (isOrbitDefined()) {
495 orbits.add(state.getOrbit());
496 } else {
497 absPvas.add(state.getAbsPVA());
498 }
499 attitudes.add(state.getAttitude());
500 massInterpolator.addSamplePoint(deltaT,
501 new double[] {
502 state.getMass()
503 });
504 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
505 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
506 }
507
508 });
509
510 // perform interpolations
511 final Orbit interpolatedOrbit;
512 final AbsolutePVCoordinates interpolatedAbsPva;
513 if (isOrbitDefined()) {
514 interpolatedOrbit = orbit.interpolate(date, orbits);
515 interpolatedAbsPva = null;
516 } else {
517 interpolatedOrbit = null;
518 interpolatedAbsPva = absPva.interpolate(date, absPvas);
519 }
520 final Attitude interpolatedAttitude = attitude.interpolate(date, attitudes);
521 final double interpolatedMass = massInterpolator.value(0)[0];
522 final Map<String, double[]> interpolatedAdditional;
523 if (additional.isEmpty()) {
524 interpolatedAdditional = null;
525 } else {
526 interpolatedAdditional = new HashMap<String, double[]>(additional.size());
527 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
528 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(0));
529 }
530 }
531
532 // create the complete interpolated state
533 if (isOrbitDefined()) {
534 return new SpacecraftState(interpolatedOrbit, interpolatedAttitude,
535 interpolatedMass, interpolatedAdditional);
536 } else {
537 return new SpacecraftState(interpolatedAbsPva, interpolatedAttitude,
538 interpolatedMass, interpolatedAdditional);
539 }
540
541 }
542
543 /** Get the absolute position-velocity-acceleration.
544 * <p>
545 * A state contains either an {@link AbsolutePVCoordinates absolute
546 * position-velocity-acceleration} or an {@link Orbit orbit}. Which
547 * one is present can be checked using {@link #isOrbitDefined()}.
548 * </p>
549 * @return absolute position-velocity-acceleration
550 * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
551 * which mean the state rather contains an {@link Orbit}
552 * @see #isOrbitDefined()
553 * @see #getOrbit()
554 */
555 public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException {
556 if (absPva == null) {
557 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
558 }
559 return absPva;
560 }
561
562 /** Get the current orbit.
563 * <p>
564 * A state contains either an {@link AbsolutePVCoordinates absolute
565 * position-velocity-acceleration} or an {@link Orbit orbit}. Which
566 * one is present can be checked using {@link #isOrbitDefined()}.
567 * </p>
568 * @return the orbit
569 * @exception OrekitIllegalStateException if orbit is null,
570 * which means the state rather contains an {@link AbsolutePVCoordinates absolute
571 * position-velocity-acceleration}
572 * @see #isOrbitDefined()
573 * @see #getAbsPVA()
574 */
575 public Orbit getOrbit() throws OrekitIllegalStateException {
576 if (orbit == null) {
577 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
578 }
579 return orbit;
580 }
581
582 /** Get the date.
583 * @return date
584 */
585 public AbsoluteDate getDate() {
586 return (absPva == null) ? orbit.getDate() : absPva.getDate();
587 }
588
589 /** Get the defining frame.
590 * @return the frame in which state is defined
591 */
592 public Frame getFrame() {
593 return (absPva == null) ? orbit.getFrame() : absPva.getFrame();
594 }
595
596 /** Check if an additional state is available.
597 * @param name name of the additional state
598 * @return true if the additional state is available
599 * @see #addAdditionalState(String, double[])
600 * @see #getAdditionalState(String)
601 * @see #getAdditionalStates()
602 */
603 public boolean hasAdditionalState(final String name) {
604 return additional.containsKey(name);
605 }
606
607 /** Check if two instances have the same set of additional states available.
608 * <p>
609 * Only the names and dimensions of the additional states are compared,
610 * not their values.
611 * </p>
612 * @param state state to compare to instance
613 * @exception MathIllegalStateException if an additional state does not have
614 * the same dimension in both states
615 */
616 public void ensureCompatibleAdditionalStates(final SpacecraftState state)
617 throws MathIllegalStateException {
618
619 // check instance additional states is a subset of the other one
620 for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
621 final double[] other = state.additional.get(entry.getKey());
622 if (other == null) {
623 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
624 entry.getKey());
625 }
626 if (other.length != entry.getValue().length) {
627 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
628 other.length, entry.getValue().length);
629 }
630 }
631
632 if (state.additional.size() > additional.size()) {
633 // the other state has more additional states
634 for (final String name : state.additional.keySet()) {
635 if (!additional.containsKey(name)) {
636 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
637 name);
638 }
639 }
640 }
641
642 }
643
644 /** Get an additional state.
645 * @param name name of the additional state
646 * @return value of the additional state
647 * @see #addAdditionalState(String, double[])
648 * @see #hasAdditionalState(String)
649 * @see #getAdditionalStates()
650 */
651 public double[] getAdditionalState(final String name) {
652 if (!additional.containsKey(name)) {
653 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
654 }
655 return additional.get(name).clone();
656 }
657
658 /** Get an unmodifiable map of additional states.
659 * @return unmodifiable map of additional states
660 * @see #addAdditionalState(String, double[])
661 * @see #hasAdditionalState(String)
662 * @see #getAdditionalState(String)
663 */
664 public Map<String, double[]> getAdditionalStates() {
665 return Collections.unmodifiableMap(additional);
666 }
667
668 /** Compute the transform from state defining frame to spacecraft frame.
669 * <p>The spacecraft frame origin is at the point defined by the orbit
670 * (or absolute position-velocity-acceleration), and its orientation is
671 * defined by the attitude.</p>
672 * @return transform from specified frame to current spacecraft frame
673 */
674 public Transform toTransform() {
675 final TimeStampedPVCoordinates pv = getPVCoordinates();
676 return new Transform(pv.getDate(),
677 new Transform(pv.getDate(), pv.negate()),
678 new Transform(pv.getDate(), attitude.getOrientation()));
679 }
680
681 /** Get the central attraction coefficient.
682 * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the
683 * state is contains an absolute position-velocity-acceleration rather than an orbit
684 */
685 public double getMu() {
686 return (absPva == null) ? orbit.getMu() : Double.NaN;
687 }
688
689 /** Get the Keplerian period.
690 * <p>The Keplerian period is computed directly from semi major axis
691 * and central acceleration constant.</p>
692 * @return keplerian period in seconds, or {code Double.NaN} if the
693 * state is contains an absolute position-velocity-acceleration rather
694 * than an orbit
695 */
696 public double getKeplerianPeriod() {
697 return (absPva == null) ? orbit.getKeplerianPeriod() : Double.NaN;
698 }
699
700 /** Get the Keplerian mean motion.
701 * <p>The Keplerian mean motion is computed directly from semi major axis
702 * and central acceleration constant.</p>
703 * @return keplerian mean motion in radians per second, or {code Double.NaN} if the
704 * state is contains an absolute position-velocity-acceleration rather
705 * than an orbit
706 */
707 public double getKeplerianMeanMotion() {
708 return (absPva == null) ? orbit.getKeplerianMeanMotion() : Double.NaN;
709 }
710
711 /** Get the semi-major axis.
712 * @return semi-major axis (m), or {code Double.NaN} if the
713 * state is contains an absolute position-velocity-acceleration rather
714 * than an orbit
715 */
716 public double getA() {
717 return (absPva == null) ? orbit.getA() : Double.NaN;
718 }
719
720 /** Get the first component of the eccentricity vector (as per equinoctial parameters).
721 * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the
722 * state is contains an absolute position-velocity-acceleration rather
723 * than an orbit
724 * @see #getE()
725 */
726 public double getEquinoctialEx() {
727 return (absPva == null) ? orbit.getEquinoctialEx() : Double.NaN;
728 }
729
730 /** Get the second component of the eccentricity vector (as per equinoctial parameters).
731 * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the
732 * state is contains an absolute position-velocity-acceleration rather
733 * than an orbit
734 * @see #getE()
735 */
736 public double getEquinoctialEy() {
737 return (absPva == null) ? orbit.getEquinoctialEy() : Double.NaN;
738 }
739
740 /** Get the first component of the inclination vector (as per equinoctial parameters).
741 * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the
742 * state is contains an absolute position-velocity-acceleration rather
743 * than an orbit
744 * @see #getI()
745 */
746 public double getHx() {
747 return (absPva == null) ? orbit.getHx() : Double.NaN;
748 }
749
750 /** Get the second component of the inclination vector (as per equinoctial parameters).
751 * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the
752 * state is contains an absolute position-velocity-acceleration rather
753 * than an orbit
754 * @see #getI()
755 */
756 public double getHy() {
757 return (absPva == null) ? orbit.getHy() : Double.NaN;
758 }
759
760 /** Get the true latitude argument (as per equinoctial parameters).
761 * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the
762 * state is contains an absolute position-velocity-acceleration rather
763 * than an orbit
764 * @see #getLE()
765 * @see #getLM()
766 */
767 public double getLv() {
768 return (absPva == null) ? orbit.getLv() : Double.NaN;
769 }
770
771 /** Get the eccentric latitude argument (as per equinoctial parameters).
772 * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the
773 * state is contains an absolute position-velocity-acceleration rather
774 * than an orbit
775 * @see #getLv()
776 * @see #getLM()
777 */
778 public double getLE() {
779 return (absPva == null) ? orbit.getLE() : Double.NaN;
780 }
781
782 /** Get the mean longitude argument (as per equinoctial parameters).
783 * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the
784 * state is contains an absolute position-velocity-acceleration rather
785 * than an orbit
786 * @see #getLv()
787 * @see #getLE()
788 */
789 public double getLM() {
790 return (absPva == null) ? orbit.getLM() : Double.NaN;
791 }
792
793 // Additional orbital elements
794
795 /** Get the eccentricity.
796 * @return eccentricity, or {code Double.NaN} if the
797 * state is contains an absolute position-velocity-acceleration rather
798 * than an orbit
799 * @see #getEquinoctialEx()
800 * @see #getEquinoctialEy()
801 */
802 public double getE() {
803 return (absPva == null) ? orbit.getE() : Double.NaN;
804 }
805
806 /** Get the inclination.
807 * @return inclination (rad)
808 * @see #getHx()
809 * @see #getHy()
810 */
811 public double getI() {
812 return (absPva == null) ? orbit.getI() : Double.NaN;
813 }
814
815 /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
816 * <p>
817 * Compute the position and velocity of the satellite. This method caches its
818 * results, and recompute them only when the method is called with a new value
819 * for mu. The result is provided as a reference to the internally cached
820 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
821 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
822 * </p>
823 * @return pvCoordinates in orbit definition frame
824 */
825 public TimeStampedPVCoordinates getPVCoordinates() {
826 return (absPva == null) ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
827 }
828
829 /** Get the {@link TimeStampedPVCoordinates} in given output frame.
830 * <p>
831 * Compute the position and velocity of the satellite. This method caches its
832 * results, and recompute them only when the method is called with a new value
833 * for mu. The result is provided as a reference to the internally cached
834 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
835 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
836 * </p>
837 * @param outputFrame frame in which coordinates should be defined
838 * @return pvCoordinates in orbit definition frame
839 */
840 public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
841 return (absPva == null) ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
842 }
843
844 /** Get the attitude.
845 * @return the attitude.
846 */
847 public Attitude getAttitude() {
848 return attitude;
849 }
850
851 /** Gets the current mass.
852 * @return the mass (kg)
853 */
854 public double getMass() {
855 return mass;
856 }
857
858 /** Replace the instance with a data transfer object for serialization.
859 * @return data transfer object that will be serialized
860 */
861 private Object writeReplace() {
862 return isOrbitDefined() ? new DTOO(this) : new DTOA(this);
863 }
864
865 /** Internal class used only for serialization. */
866 private static class DTOO implements Serializable {
867
868 /** Serializable UID. */
869 private static final long serialVersionUID = 20150916L;
870
871 /** Orbit. */
872 private final Orbit orbit;
873
874 /** Attitude and mass double values. */
875 private double[] d;
876
877 /** Additional states. */
878 private final Map<String, double[]> additional;
879
880 /** Simple constructor.
881 * @param state instance to serialize
882 */
883 private DTOO(final SpacecraftState state) {
884
885 this.orbit = state.orbit;
886 this.additional = state.additional.isEmpty() ? null : state.additional;
887
888 final Rotation rotation = state.attitude.getRotation();
889 final Vector3D spin = state.attitude.getSpin();
890 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
891 this.d = new double[] {
892 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
893 spin.getX(), spin.getY(), spin.getZ(),
894 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
895 state.mass
896 };
897
898 }
899
900 /** Replace the de-serialized data transfer object with a {@link SpacecraftState}.
901 * @return replacement {@link SpacecraftState}
902 */
903 private Object readResolve() {
904 return new SpacecraftState(orbit,
905 new Attitude(orbit.getFrame(),
906 new TimeStampedAngularCoordinates(orbit.getDate(),
907 new Rotation(d[0], d[1], d[2], d[3], false),
908 new Vector3D(d[4], d[5], d[6]),
909 new Vector3D(d[7], d[8], d[9]))),
910 d[10], additional);
911 }
912
913 }
914
915 /** Internal class used only for serialization. */
916 private static class DTOA implements Serializable {
917
918 /** Serializable UID. */
919 private static final long serialVersionUID = 20150916L;
920
921 /** Absolute position-velocity-acceleration. */
922 private final AbsolutePVCoordinates absPva;
923
924 /** Attitude and mass double values. */
925 private double[] d;
926
927 /** Additional states. */
928 private final Map<String, double[]> additional;
929
930 /** Simple constructor.
931 * @param state instance to serialize
932 */
933 private DTOA(final SpacecraftState state) {
934
935 this.absPva = state.absPva;
936 this.additional = state.additional.isEmpty() ? null : state.additional;
937
938 final Rotation rotation = state.attitude.getRotation();
939 final Vector3D spin = state.attitude.getSpin();
940 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
941 this.d = new double[] {
942 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
943 spin.getX(), spin.getY(), spin.getZ(),
944 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
945 state.mass
946 };
947
948 }
949
950 /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
951 * @return replacement {@link SpacecraftState}
952 */
953 private Object readResolve() {
954 return new SpacecraftState(absPva,
955 new Attitude(absPva.getFrame(),
956 new TimeStampedAngularCoordinates(absPva.getDate(),
957 new Rotation(d[0], d[1], d[2], d[3], false),
958 new Vector3D(d[4], d[5], d[6]),
959 new Vector3D(d[7], d[8], d[9]))),
960 d[10], additional);
961 }
962 }
963
964 @Override
965 public String toString() {
966 return "SpacecraftState{" +
967 "orbit=" + orbit +
968 ", attitude=" + attitude +
969 ", mass=" + mass +
970 ", additional=" + additional +
971 '}';
972 }
973 }