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