1 /* Copyright 2002-2025 CS GROUP 2 * Licensed to CS GROUP (CS) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * CS licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.orekit.propagation; 18 19 import org.hipparchus.exception.LocalizedCoreFormats; 20 import org.hipparchus.exception.MathIllegalStateException; 21 import org.hipparchus.geometry.euclidean.threed.Vector3D; 22 import org.hipparchus.util.FastMath; 23 import org.orekit.attitudes.Attitude; 24 import org.orekit.attitudes.AttitudeProvider; 25 import org.orekit.attitudes.FrameAlignedProvider; 26 import org.orekit.errors.OrekitException; 27 import org.orekit.errors.OrekitIllegalArgumentException; 28 import org.orekit.errors.OrekitIllegalStateException; 29 import org.orekit.errors.OrekitMessages; 30 import org.orekit.frames.Frame; 31 import org.orekit.frames.StaticTransform; 32 import org.orekit.frames.Transform; 33 import org.orekit.orbits.Orbit; 34 import org.orekit.propagation.numerical.NumericalPropagator; 35 import org.orekit.time.AbsoluteDate; 36 import org.orekit.time.TimeOffset; 37 import org.orekit.time.TimeShiftable; 38 import org.orekit.time.TimeStamped; 39 import org.orekit.utils.AbsolutePVCoordinates; 40 import org.orekit.utils.DataDictionary; 41 import org.orekit.utils.DoubleArrayDictionary; 42 import org.orekit.utils.TimeStampedPVCoordinates; 43 44 /** This class is the representation of a complete state holding orbit, attitude 45 * and mass information at a given date, meant primarily for propagation. 46 * 47 * <p>It contains an {@link Orbit}, or an {@link AbsolutePVCoordinates} if there 48 * is no definite central body, plus the current mass and attitude at the intrinsic 49 * {@link AbsoluteDate}. Quantities are guaranteed to be consistent in terms 50 * of date and reference frame. The spacecraft state may also contain additional 51 * data, which are simply named. 52 * </p> 53 * <p> 54 * The state can be slightly shifted to close dates. This actual shift varies 55 * between {@link Orbit} and {@link AbsolutePVCoordinates}. 56 * For attitude it is a linear extrapolation taking the spin rate into account 57 * and no mass change. It is <em>not</em> intended as a replacement for proper 58 * orbit and attitude propagation but should be sufficient for either small 59 * time shifts or coarse accuracy. 60 * </p> 61 * <p> 62 * The instance <code>SpacecraftState</code> is guaranteed to be immutable. 63 * </p> 64 * @see NumericalPropagator 65 * @author Fabien Maussion 66 * @author Véronique Pommier-Maurussane 67 * @author Luc Maisonobe 68 */ 69 public class SpacecraftState implements TimeStamped, TimeShiftable<SpacecraftState> { 70 71 /** Default mass. */ 72 public static final double DEFAULT_MASS = 1000.0; 73 74 /** 75 * tolerance on date comparison in {@link #checkConsistency(Orbit, Attitude)}. 100 ns 76 * corresponds to sub-mm accuracy at LEO orbital velocities. 77 */ 78 private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9; 79 80 /** Orbital state. */ 81 private final Orbit orbit; 82 83 /** Trajectory state, when it is not an orbit. */ 84 private final AbsolutePVCoordinates absPva; 85 86 /** Attitude. */ 87 private final Attitude attitude; 88 89 /** Current mass (kg). */ 90 private final double mass; 91 92 /** Additional data, can be any object (String, double[], etc.). */ 93 private final DataDictionary additional; 94 95 /** Additional states derivatives. 96 * @since 11.1 97 */ 98 private final DoubleArrayDictionary additionalDot; 99 100 /** Build a spacecraft state from orbit only. 101 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p> 102 * @param orbit the orbit 103 */ 104 public SpacecraftState(final Orbit orbit) { 105 this(orbit, getDefaultAttitudeProvider(orbit.getFrame()) 106 .getAttitude(orbit, orbit.getDate(), orbit.getFrame()), 107 DEFAULT_MASS, null, null); 108 } 109 110 /** Build a spacecraft state from orbit and attitude. Kept for performance. 111 * <p>Mass is set to an unspecified non-null arbitrary value.</p> 112 * @param orbit the orbit 113 * @param attitude attitude 114 * @exception IllegalArgumentException if orbit and attitude dates 115 * or frames are not equal 116 */ 117 public SpacecraftState(final Orbit orbit, final Attitude attitude) 118 throws IllegalArgumentException { 119 this(orbit, attitude, DEFAULT_MASS, null, null); 120 } 121 122 /** Create a new instance from orbit and mass. 123 * <p>Attitude law is set to an unspecified default attitude.</p> 124 * @param orbit the orbit 125 * @param mass the mass (kg) 126 * @deprecated since 13.0, use withXXX 127 */ 128 @Deprecated 129 public SpacecraftState(final Orbit orbit, final double mass) { 130 this(orbit, getDefaultAttitudeProvider(orbit.getFrame()) 131 .getAttitude(orbit, orbit.getDate(), orbit.getFrame()), 132 mass, null, null); 133 } 134 135 /** Build a spacecraft state from orbit, attitude and mass. 136 * @param orbit the orbit 137 * @param attitude attitude 138 * @param mass the mass (kg) 139 * @exception IllegalArgumentException if orbit and attitude dates 140 * or frames are not equal 141 * @deprecated since 13.0, use withXXX 142 */ 143 @Deprecated 144 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass) 145 throws IllegalArgumentException { 146 this(orbit, attitude, mass, null, null); 147 } 148 149 /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives. 150 * @param orbit the orbit 151 * @param attitude attitude 152 * @param mass the mass (kg) 153 * @param additional additional data (may be null if no additional states are available) 154 * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available) 155 * @exception IllegalArgumentException if orbit and attitude dates 156 * or frames are not equal 157 * @since 13.0 158 */ 159 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass, 160 final DataDictionary additional, final DoubleArrayDictionary additionalDot) 161 throws IllegalArgumentException { 162 checkConsistency(orbit, attitude); 163 this.orbit = orbit; 164 this.absPva = null; 165 this.attitude = attitude; 166 this.mass = mass; 167 this.additional = additional == null ? new DataDictionary() : additional; 168 this.additionalDot = additionalDot == null ? new DoubleArrayDictionary() : new DoubleArrayDictionary(additionalDot); 169 } 170 171 /** Build a spacecraft state from position-velocity-acceleration only. 172 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p> 173 * @param absPva position-velocity-acceleration 174 */ 175 public SpacecraftState(final AbsolutePVCoordinates absPva) { 176 this(absPva, getDefaultAttitudeProvider(absPva.getFrame()) 177 .getAttitude(absPva, absPva.getDate(), absPva.getFrame()), 178 DEFAULT_MASS, null, null); 179 } 180 181 /** Build a spacecraft state from position-velocity-acceleration and attitude. Kept for performance. 182 * <p>Mass is set to an unspecified non-null arbitrary value.</p> 183 * @param absPva position-velocity-acceleration 184 * @param attitude attitude 185 * @exception IllegalArgumentException if orbit and attitude dates 186 * or frames are not equal 187 */ 188 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude) 189 throws IllegalArgumentException { 190 this(absPva, attitude, DEFAULT_MASS, null, null); 191 } 192 193 /** Create a new instance from position-velocity-acceleration and mass. 194 * <p>Attitude law is set to an unspecified default attitude.</p> 195 * @param absPva position-velocity-acceleration 196 * @param mass the mass (kg) 197 * @deprecated since 13.0, use withXXX 198 */ 199 @Deprecated 200 public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) { 201 this(absPva, getDefaultAttitudeProvider(absPva.getFrame()) 202 .getAttitude(absPva, absPva.getDate(), absPva.getFrame()), 203 mass, null, null); 204 } 205 206 /** Build a spacecraft state from position-velocity-acceleration, attitude and mass. 207 * @param absPva position-velocity-acceleration 208 * @param attitude attitude 209 * @param mass the mass (kg) 210 * @exception IllegalArgumentException if orbit and attitude dates 211 * or frames are not equal 212 * @deprecated since 13.0, use withXXX 213 */ 214 @Deprecated 215 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass) 216 throws IllegalArgumentException { 217 this(absPva, attitude, mass, null, null); 218 } 219 220 /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states and derivatives. 221 * @param absPva position-velocity-acceleration 222 * @param attitude attitude 223 * @param mass the mass (kg) 224 * @param additional additional data (may be null if no additional data are available) 225 * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available) 226 * @exception IllegalArgumentException if orbit and attitude dates 227 * or frames are not equal 228 * @since 13.0 229 */ 230 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass, 231 final DataDictionary additional, final DoubleArrayDictionary additionalDot) 232 throws IllegalArgumentException { 233 checkConsistency(absPva, attitude); 234 this.orbit = null; 235 this.absPva = absPva; 236 this.attitude = attitude; 237 this.mass = mass; 238 this.additional = additional == null ? new DataDictionary() : new DataDictionary(additional); 239 this.additionalDot = additionalDot == null ? new DoubleArrayDictionary() : new DoubleArrayDictionary(additionalDot); 240 } 241 242 /** 243 * Create a new instance with input mass. 244 * @param newMass mass 245 * @return new state 246 * @since 13.0 247 */ 248 public SpacecraftState withMass(final double newMass) { 249 if (isOrbitDefined()) { 250 return new SpacecraftState(orbit, attitude, newMass, additional, additionalDot); 251 } else { 252 return new SpacecraftState(absPva, attitude, newMass, additional, additionalDot); 253 } 254 } 255 256 /** 257 * Create a new instance with input attitude. 258 * @param newAttitude attitude 259 * @return new state 260 * @since 13.0 261 */ 262 public SpacecraftState withAttitude(final Attitude newAttitude) { 263 if (isOrbitDefined()) { 264 return new SpacecraftState(orbit, newAttitude, mass, additional, additionalDot); 265 } else { 266 return new SpacecraftState(absPva, newAttitude, mass, additional, additionalDot); 267 } 268 } 269 270 /** 271 * Create a new instance with input additional data. 272 * @param dataDictionary additional data 273 * @return new state 274 * @since 13.0 275 */ 276 public SpacecraftState withAdditionalData(final DataDictionary dataDictionary) { 277 if (isOrbitDefined()) { 278 return new SpacecraftState(orbit, attitude, mass, dataDictionary, additionalDot); 279 } else { 280 return new SpacecraftState(absPva, attitude, mass, dataDictionary, additionalDot); 281 } 282 } 283 284 /** 285 * Create a new instance with input additional data. 286 * @param additionalStateDerivatives additional state derivatives 287 * @return new state 288 * @since 13.0 289 */ 290 public SpacecraftState withAdditionalStatesDerivatives(final DoubleArrayDictionary additionalStateDerivatives) { 291 if (isOrbitDefined()) { 292 return new SpacecraftState(orbit, attitude, mass, additional, additionalStateDerivatives); 293 } else { 294 return new SpacecraftState(absPva, attitude, mass, additional, additionalStateDerivatives); 295 } 296 } 297 298 /** Add an additional data. 299 * <p> 300 * {@link SpacecraftState SpacecraftState} instances are immutable, 301 * so this method does <em>not</em> change the instance, but rather 302 * creates a new instance, which has the same orbit, attitude, mass 303 * and additional states as the original instance, except it also 304 * has the specified state. If the original instance already had an 305 * additional data with the same name, it will be overridden. If it 306 * did not have any additional state with that name, the new instance 307 * will have one more additional state than the original instance. 308 * </p> 309 * @param name name of the additional data (names containing "orekit" 310 * with any case are reserved for the library internal use) 311 * @param value value of the additional data 312 * @return a new instance, with the additional data added 313 * @see #hasAdditionalData(String) 314 * @see #getAdditionalData(String) 315 * @see #getAdditionalDataValues() 316 * @since 13.0 317 */ 318 public SpacecraftState addAdditionalData(final String name, final Object value) { 319 final DataDictionary newDict = new DataDictionary(additional); 320 if (value instanceof double[]) { 321 newDict.put(name, ((double[]) value).clone()); 322 } else if (value instanceof Double) { 323 newDict.put(name, new double[] {(double) value}); 324 } else { 325 newDict.put(name, value); 326 } 327 return withAdditionalData(newDict); 328 } 329 330 /** Add an additional state derivative. 331 * <p> 332 * {@link SpacecraftState SpacecraftState} instances are immutable, 333 * so this method does <em>not</em> change the instance, but rather 334 * creates a new instance, which has the same components as the original 335 * instance, except it also has the specified state derivative. If the 336 * original instance already had an additional state derivative with the 337 * same name, it will be overridden. If it did not have any additional 338 * state derivative with that name, the new instance will have one more 339 * additional state derivative than the original instance. 340 * </p> 341 * @param name name of the additional state derivative (names containing "orekit" 342 * with any case are reserved for the library internal use) 343 * @param value value of the additional state derivative 344 * @return a new instance, with the additional state added 345 * @see #hasAdditionalStateDerivative(String) 346 * @see #getAdditionalStateDerivative(String) 347 * @see #getAdditionalStatesDerivatives() 348 * @since 11.1 349 */ 350 public SpacecraftState addAdditionalStateDerivative(final String name, final double... value) { 351 final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additionalDot); 352 newDict.put(name, value.clone()); 353 return withAdditionalStatesDerivatives(newDict); 354 } 355 356 /** Check orbit and attitude dates are equal. 357 * @param orbit the orbit 358 * @param attitude attitude 359 * @exception IllegalArgumentException if orbit and attitude dates 360 * are not equal 361 */ 362 private static void checkConsistency(final Orbit orbit, final Attitude attitude) 363 throws IllegalArgumentException { 364 checkDateAndFrameConsistency(attitude, orbit.getDate(), orbit.getFrame()); 365 } 366 367 /** Defines provider for default Attitude when not passed to constructor. 368 * Currently chosen arbitrarily as aligned with input frame. 369 * It is also used in {@link FieldSpacecraftState}. 370 * @param frame reference frame 371 * @return default attitude provider 372 * @since 12.0 373 */ 374 static AttitudeProvider getDefaultAttitudeProvider(final Frame frame) { 375 return new FrameAlignedProvider(frame); 376 } 377 378 /** Check if the state contains an orbit part. 379 * <p> 380 * A state contains either an {@link AbsolutePVCoordinates absolute 381 * position-velocity-acceleration} or an {@link Orbit orbit}. 382 * </p> 383 * @return true if state contains an orbit (in which case {@link #getOrbit()} 384 * will not throw an exception), or false if the state contains an 385 * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()} 386 * will not throw an exception) 387 */ 388 public boolean isOrbitDefined() { 389 return orbit != null; 390 } 391 392 /** Check AbsolutePVCoordinates and attitude dates are equal. 393 * @param absPva position-velocity-acceleration 394 * @param attitude attitude 395 * @exception IllegalArgumentException if orbit and attitude dates 396 * are not equal 397 */ 398 private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude) 399 throws IllegalArgumentException { 400 checkDateAndFrameConsistency(attitude, absPva.getDate(), absPva.getFrame()); 401 } 402 403 /** Check attitude frame and epoch. 404 * @param attitude attitude 405 * @param date epoch to verify 406 * @param frame frame to verify 407 */ 408 private static void checkDateAndFrameConsistency(final Attitude attitude, final AbsoluteDate date, final Frame frame) { 409 if (FastMath.abs(date.durationFrom(attitude.getDate())) > 410 DATE_INCONSISTENCY_THRESHOLD) { 411 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH, 412 date, attitude.getDate()); 413 } 414 if (frame != attitude.getReferenceFrame()) { 415 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH, 416 frame.getName(), 417 attitude.getReferenceFrame().getName()); 418 } 419 } 420 421 /** Get a time-shifted state. 422 * <p> 423 * The state can be slightly shifted to close dates. This shift is based on 424 * simple models. For orbits, the model is a Keplerian one if no derivatives 425 * are available in the orbit, or Keplerian plus quadratic effect of the 426 * non-Keplerian acceleration if derivatives are available. For attitude, 427 * a polynomial model is used. Neither mass nor additional states change. 428 * Shifting is <em>not</em> intended as a replacement for proper orbit 429 * and attitude propagation but should be sufficient for small time shifts 430 * or coarse accuracy. 431 * </p> 432 * <p> 433 * As a rough order of magnitude, the following table shows the extrapolation 434 * errors obtained between this simple shift method and an {@link 435 * NumericalPropagator numerical 436 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field, 437 * Sun and Moon third bodies attractions, drag and solar radiation pressure. 438 * Beware that these results will be different for other orbits. 439 * </p> 440 * <table border="1"> 441 * <caption>Extrapolation Error</caption> 442 * <tr style="background-color: #ccccff"><th>interpolation time (s)</th> 443 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr> 444 * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td> 18</td><td> 1.1</td></tr> 445 * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td> 72</td><td> 9.1</td></tr> 446 * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr> 447 * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr> 448 * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr> 449 * </table> 450 * @param dt time shift in seconds 451 * @return a new state, shifted with respect to the instance (which is immutable) 452 * except for the mass and additional states which stay unchanged 453 */ 454 @Override 455 public SpacecraftState shiftedBy(final double dt) { 456 if (isOrbitDefined()) { 457 return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt), 458 mass, shiftAdditional(dt), additionalDot); 459 } else { 460 return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt), 461 mass, shiftAdditional(dt), additionalDot); 462 } 463 } 464 465 /** Get a time-shifted state. 466 * <p> 467 * The state can be slightly shifted to close dates. This shift is based on 468 * simple models. For orbits, the model is a Keplerian one if no derivatives 469 * are available in the orbit, or Keplerian plus quadratic effect of the 470 * non-Keplerian acceleration if derivatives are available. For attitude, 471 * a polynomial model is used. Neither mass nor additional states change. 472 * Shifting is <em>not</em> intended as a replacement for proper orbit 473 * and attitude propagation but should be sufficient for small time shifts 474 * or coarse accuracy. 475 * </p> 476 * <p> 477 * As a rough order of magnitude, the following table shows the extrapolation 478 * errors obtained between this simple shift method and an {@link 479 * NumericalPropagator numerical 480 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field, 481 * Sun and Moon third bodies attractions, drag and solar radiation pressure. 482 * Beware that these results will be different for other orbits. 483 * </p> 484 * <table border="1"> 485 * <caption>Extrapolation Error</caption> 486 * <tr style="background-color: #ccccff"><th>interpolation time (s)</th> 487 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr> 488 * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td> 18</td><td> 1.1</td></tr> 489 * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td> 72</td><td> 9.1</td></tr> 490 * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr> 491 * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr> 492 * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr> 493 * </table> 494 * @param dt time shift in seconds 495 * @return a new state, shifted with respect to the instance (which is immutable) 496 * except for the mass and additional states which stay unchanged 497 * @since 13.0 498 */ 499 @Override 500 public SpacecraftState shiftedBy(final TimeOffset dt) { 501 if (isOrbitDefined()) { 502 return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt), 503 mass, shiftAdditional(dt.toDouble()), additionalDot); 504 } else { 505 return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt), 506 mass, shiftAdditional(dt.toDouble()), additionalDot); 507 } 508 } 509 510 /** Shift additional states. 511 * @param dt time shift in seconds 512 * @return shifted additional states 513 * @since 11.1.1 514 */ 515 private DataDictionary shiftAdditional(final double dt) { 516 517 // fast handling when there are no derivatives at all 518 if (additionalDot.size() == 0) { 519 return additional; 520 } 521 522 // there are derivatives, we need to take them into account in the additional state 523 final DataDictionary shifted = new DataDictionary(additional); 524 for (final DoubleArrayDictionary.Entry dotEntry : additionalDot.getData()) { 525 final DataDictionary.Entry entry = shifted.getEntry(dotEntry.getKey()); 526 if (entry != null) { 527 entry.scaledIncrement(dt, dotEntry); 528 } 529 } 530 531 return shifted; 532 533 } 534 535 /** Get the absolute position-velocity-acceleration. 536 * <p> 537 * A state contains either an {@link AbsolutePVCoordinates absolute 538 * position-velocity-acceleration} or an {@link Orbit orbit}. Which 539 * one is present can be checked using {@link #isOrbitDefined()}. 540 * </p> 541 * @return absolute position-velocity-acceleration 542 * @exception OrekitIllegalStateException if position-velocity-acceleration is null, 543 * which mean the state rather contains an {@link Orbit} 544 * @see #isOrbitDefined() 545 * @see #getOrbit() 546 */ 547 public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException { 548 if (isOrbitDefined()) { 549 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES); 550 } 551 return absPva; 552 } 553 554 /** Get the current orbit. 555 * <p> 556 * A state contains either an {@link AbsolutePVCoordinates absolute 557 * position-velocity-acceleration} or an {@link Orbit orbit}. Which 558 * one is present can be checked using {@link #isOrbitDefined()}. 559 * </p> 560 * @return the orbit 561 * @exception OrekitIllegalStateException if orbit is null, 562 * which means the state rather contains an {@link AbsolutePVCoordinates absolute 563 * position-velocity-acceleration} 564 * @see #isOrbitDefined() 565 * @see #getAbsPVA() 566 */ 567 public Orbit getOrbit() throws OrekitIllegalStateException { 568 if (orbit == null) { 569 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT); 570 } 571 return orbit; 572 } 573 574 /** {@inheritDoc} */ 575 @Override 576 public AbsoluteDate getDate() { 577 return (absPva == null) ? orbit.getDate() : absPva.getDate(); 578 } 579 580 /** Get the defining frame. 581 * @return the frame in which state is defined 582 */ 583 public Frame getFrame() { 584 return isOrbitDefined() ? orbit.getFrame() : absPva.getFrame(); 585 } 586 587 /** Check if an additional data is available. 588 * @param name name of the additional data 589 * @return true if the additional data is available 590 * @see #addAdditionalData(String, Object) 591 * @see #getAdditionalState(String) 592 * @see #getAdditionalData(String) 593 * @see #getAdditionalDataValues() 594 */ 595 public boolean hasAdditionalData(final String name) { 596 return additional.getEntry(name) != null; 597 } 598 599 /** Check if an additional state derivative is available. 600 * @param name name of the additional state derivative 601 * @return true if the additional state derivative is available 602 * @see #addAdditionalStateDerivative(String, double[]) 603 * @see #getAdditionalStateDerivative(String) 604 * @see #getAdditionalStatesDerivatives() 605 * @since 11.1 606 */ 607 public boolean hasAdditionalStateDerivative(final String name) { 608 return additionalDot.getEntry(name) != null; 609 } 610 611 /** Check if two instances have the same set of additional states available. 612 * <p> 613 * Only the names and dimensions of the additional states are compared, 614 * not their values. 615 * </p> 616 * @param state state to compare to instance 617 * @exception MathIllegalStateException if an additional state does not have 618 * the same dimension in both states 619 */ 620 public void ensureCompatibleAdditionalStates(final SpacecraftState state) 621 throws MathIllegalStateException { 622 623 // check instance additional states is a subset of the other one 624 for (final DataDictionary.Entry entry : additional.getData()) { 625 final Object other = state.additional.get(entry.getKey()); 626 if (other == null || !entry.getValue().getClass().equals(other.getClass())) { 627 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, 628 entry.getKey()); 629 } 630 if (other instanceof double[] && ((double[]) other).length != ((double[]) entry.getValue()).length) { 631 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH, 632 ((double[]) other).length, ((double[]) entry.getValue()).length); 633 } 634 } 635 636 // check instance additional states derivatives is a subset of the other one 637 for (final DoubleArrayDictionary.Entry entry : additionalDot.getData()) { 638 final double[] other = state.additionalDot.get(entry.getKey()); 639 if (other == null) { 640 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, 641 entry.getKey()); 642 } 643 if (other.length != entry.getValue().length) { 644 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH, 645 other.length, entry.getValue().length); 646 } 647 } 648 649 if (state.additional.size() > additional.size()) { 650 // the other state has more additional states 651 for (final DataDictionary.Entry entry : state.additional.getData()) { 652 if (additional.getEntry(entry.getKey()) == null) { 653 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, 654 entry.getKey()); 655 } 656 } 657 } 658 659 if (state.additionalDot.size() > additionalDot.size()) { 660 // the other state has more additional states 661 for (final DoubleArrayDictionary.Entry entry : state.additionalDot.getData()) { 662 if (additionalDot.getEntry(entry.getKey()) == null) { 663 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, 664 entry.getKey()); 665 } 666 } 667 } 668 669 } 670 671 /** 672 * Get an additional state. 673 * 674 * @param name name of the additional state 675 * @return value of the additional state 676 * @see #hasAdditionalData(String) 677 * @see #getAdditionalDataValues() 678 */ 679 public double[] getAdditionalState(final String name) { 680 final Object data = getAdditionalData(name); 681 if (!(data instanceof double[])) { 682 if (data instanceof Double) { 683 return new double[] {(double) data}; 684 } else { 685 throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_BAD_TYPE, name); 686 } 687 } 688 return (double[]) data; 689 } 690 691 /** 692 * Get an additional data. 693 * 694 * @param name name of the additional state 695 * @return value of the additional state 696 * @see #addAdditionalData(String, Object) 697 * @see #hasAdditionalData(String) 698 * @see #getAdditionalDataValues() 699 * @since 13.0 700 */ 701 public Object getAdditionalData(final String name) { 702 final DataDictionary.Entry entry = additional.getEntry(name); 703 if (entry == null) { 704 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name); 705 } 706 return entry.getValue(); 707 } 708 709 /** Get an additional state derivative. 710 * @param name name of the additional state derivative 711 * @return value of the additional state derivative 712 * @see #addAdditionalStateDerivative(String, double[]) 713 * @see #hasAdditionalStateDerivative(String) 714 * @see #getAdditionalStatesDerivatives() 715 * @since 11.1 716 */ 717 public double[] getAdditionalStateDerivative(final String name) { 718 final DoubleArrayDictionary.Entry entry = additionalDot.getEntry(name); 719 if (entry == null) { 720 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name); 721 } 722 return entry.getValue(); 723 } 724 725 /** Get an unmodifiable map of additional data. 726 * @return unmodifiable map of additional data 727 * @see #addAdditionalData(String, Object) 728 * @see #hasAdditionalData(String) 729 * @see #getAdditionalState(String) 730 * @since 11.1 731 */ 732 public DataDictionary getAdditionalDataValues() { 733 return additional; 734 } 735 736 /** Get an unmodifiable map of additional states derivatives. 737 * @return unmodifiable map of additional states derivatives 738 * @see #addAdditionalStateDerivative(String, double[]) 739 * @see #hasAdditionalStateDerivative(String) 740 * @see #getAdditionalStateDerivative(String) 741 * @since 11.1 742 */ 743 public DoubleArrayDictionary getAdditionalStatesDerivatives() { 744 return additionalDot; 745 } 746 747 /** Compute the transform from state defining frame to spacecraft frame. 748 * <p>The spacecraft frame origin is at the point defined by the orbit 749 * (or absolute position-velocity-acceleration), and its orientation is 750 * defined by the attitude.</p> 751 * @return transform from specified frame to current spacecraft frame 752 */ 753 public Transform toTransform() { 754 final TimeStampedPVCoordinates pv = getPVCoordinates(); 755 return new Transform(pv.getDate(), pv.negate(), attitude.getOrientation()); 756 } 757 758 /** Compute the static transform from state defining frame to spacecraft frame. 759 * @return static transform from specified frame to current spacecraft frame 760 * @see #toTransform() 761 * @since 12.0 762 */ 763 public StaticTransform toStaticTransform() { 764 return StaticTransform.of(getDate(), getPosition().negate(), attitude.getRotation()); 765 } 766 767 /** Get the position in orbit definition frame. 768 * @return position in orbit definition frame 769 * @since 12.0 770 * @see #getPVCoordinates() 771 */ 772 public Vector3D getPosition() { 773 return isOrbitDefined() ? orbit.getPosition() : absPva.getPosition(); 774 } 775 776 /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame. 777 * <p> 778 * Compute the position and velocity of the satellite. This method caches its 779 * results, and recompute them only when the method is called with a new value 780 * for mu. The result is provided as a reference to the internally cached 781 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate 782 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while. 783 * </p> 784 * @return pvCoordinates in orbit definition frame 785 */ 786 public TimeStampedPVCoordinates getPVCoordinates() { 787 return isOrbitDefined() ? orbit.getPVCoordinates() : absPva.getPVCoordinates(); 788 } 789 790 /** Get the position in given output frame. 791 * @param outputFrame frame in which position should be defined 792 * @return position in given output frame 793 * @since 12.0 794 * @see #getPVCoordinates(Frame) 795 */ 796 public Vector3D getPosition(final Frame outputFrame) { 797 return isOrbitDefined() ? orbit.getPosition(outputFrame) : absPva.getPosition(outputFrame); 798 } 799 800 /** Get the {@link TimeStampedPVCoordinates} in given output frame. 801 * <p> 802 * Compute the position and velocity of the satellite. This method caches its 803 * results, and recompute them only when the method is called with a new value 804 * for mu. The result is provided as a reference to the internally cached 805 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate 806 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while. 807 * </p> 808 * @param outputFrame frame in which coordinates should be defined 809 * @return pvCoordinates in given output frame 810 */ 811 public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) { 812 return isOrbitDefined() ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame); 813 } 814 815 /** Get the attitude. 816 * @return the attitude. 817 */ 818 public Attitude getAttitude() { 819 return attitude; 820 } 821 822 /** Gets the current mass. 823 * @return the mass (kg) 824 */ 825 public double getMass() { 826 return mass; 827 } 828 829 @Override 830 public String toString() { 831 return "SpacecraftState{" + 832 "orbit=" + orbit + 833 ", attitude=" + attitude + 834 ", mass=" + mass + 835 ", additional=" + additional + 836 ", additionalDot=" + additionalDot + 837 '}'; 838 } 839 }