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.analytical;
18  
19  import org.hipparchus.linear.RealMatrix;
20  import org.orekit.attitudes.Attitude;
21  import org.orekit.attitudes.AttitudeProvider;
22  import org.orekit.attitudes.FrameAlignedProvider;
23  import org.orekit.orbits.Orbit;
24  import org.orekit.orbits.OrbitType;
25  import org.orekit.orbits.PositionAngleType;
26  import org.orekit.propagation.AbstractMatricesHarvester;
27  import org.orekit.propagation.SpacecraftState;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.utils.DoubleArrayDictionary;
30  import org.orekit.utils.DataDictionary;
31  import org.orekit.utils.TimeSpanMap;
32  
33  /** Simple Keplerian orbit propagator.
34   * @see Orbit
35   * @author Guylaine Prat
36   */
37  public class KeplerianPropagator extends AbstractAnalyticalPropagator {
38  
39      /** All states. */
40      private TimeSpanMap<SpacecraftState> states;
41  
42      /** Build a propagator from orbit only.
43       * <p>The central attraction coefficient μ is set to the same value used
44       * for the initial orbit definition. Mass and attitude provider are set to
45       * unspecified non-null arbitrary values.</p>
46       *
47       * @param initialOrbit initial orbit
48       * @see #KeplerianPropagator(Orbit, AttitudeProvider)
49       */
50      public KeplerianPropagator(final Orbit initialOrbit) {
51          this(initialOrbit, FrameAlignedProvider.of(initialOrbit.getFrame()),
52               initialOrbit.getMu(), DEFAULT_MASS);
53      }
54  
55      /** Build a propagator from orbit and central attraction coefficient μ.
56       * <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
57       *
58       * @param initialOrbit initial orbit
59       * @param mu central attraction coefficient (m³/s²)
60       * @see #KeplerianPropagator(Orbit, AttitudeProvider, double)
61       */
62      public KeplerianPropagator(final Orbit initialOrbit, final double mu) {
63          this(initialOrbit, FrameAlignedProvider.of(initialOrbit.getFrame()),
64               mu, DEFAULT_MASS);
65      }
66  
67      /** Build a propagator from orbit and attitude provider.
68       * <p>The central attraction coefficient μ is set to the same value
69       * used for the initial orbit definition. Mass is set to an unspecified
70       * non-null arbitrary value.</p>
71       * @param initialOrbit initial orbit
72       * @param attitudeProv  attitude provider
73       */
74      public KeplerianPropagator(final Orbit initialOrbit,
75                                 final AttitudeProvider attitudeProv) {
76          this(initialOrbit, attitudeProv, initialOrbit.getMu(), DEFAULT_MASS);
77      }
78  
79      /** Build a propagator from orbit, attitude provider and central attraction
80       * coefficient μ.
81       * <p>Mass is set to an unspecified non-null arbitrary value.</p>
82       * @param initialOrbit initial orbit
83       * @param attitudeProv attitude provider
84       * @param mu central attraction coefficient (m³/s²)
85       */
86      public KeplerianPropagator(final Orbit initialOrbit,
87                                 final AttitudeProvider attitudeProv,
88                                 final double mu) {
89          this(initialOrbit, attitudeProv, mu, DEFAULT_MASS);
90      }
91  
92      /** Build propagator from orbit, attitude provider, central attraction
93       * coefficient μ and mass.
94       * @param initialOrbit initial orbit
95       * @param attitudeProv attitude provider
96       * @param mu central attraction coefficient (m³/s²)
97       * @param mass spacecraft mass (kg)
98       */
99      public KeplerianPropagator(final Orbit initialOrbit, final AttitudeProvider attitudeProv,
100                                final double mu, final double mass) {
101 
102         super(attitudeProv);
103 
104         // ensure the orbit use the specified mu and has no non-Keplerian derivatives
105         final SpacecraftState initial = fixState(initialOrbit,
106                                                  getAttitudeProvider().getAttitude(initialOrbit,
107                                                                                    initialOrbit.getDate(),
108                                                                                    initialOrbit.getFrame()),
109                                                  mass, mu, null, null);
110         states = new TimeSpanMap<>(initial);
111         super.resetInitialState(initial);
112 
113     }
114 
115     /** Fix state to use a specified mu and remove derivatives.
116      * <p>
117      * This ensures the propagation model (which is based on calling
118      * {@link Orbit#shiftedBy(double)}) is Keplerian only and uses a specified mu.
119      * </p>
120      * @param orbit orbit to fix
121      * @param attitude current attitude
122      * @param mass current mass
123      * @param mu gravity coefficient to use
124      * @param additionalStates additional states (may be null)
125      * @param additionalStatesDerivatives additional states derivatives (may be null)
126      * @return fixed orbit
127      */
128     private SpacecraftState fixState(final Orbit orbit, final Attitude attitude, final double mass, final double mu,
129                                      final DataDictionary additionalStates,
130                                      final DoubleArrayDictionary additionalStatesDerivatives) {
131         final OrbitType type = orbit.getType();
132         final double[] stateVector = new double[6];
133         final PositionAngleType positionAngleType = PositionAngleType.MEAN;
134         type.mapOrbitToArray(orbit, positionAngleType, stateVector, null);
135         final Orbit fixedOrbit = type.mapArrayToOrbit(stateVector, null, positionAngleType,
136                                                       orbit.getDate(), mu, orbit.getFrame());
137         SpacecraftState fixedState = new SpacecraftState(fixedOrbit, attitude).withMass(mass);
138         if (additionalStates != null) {
139             for (final DataDictionary.Entry entry : additionalStates.getData()) {
140                 fixedState = fixedState.addAdditionalData(entry.getKey(), entry.getValue());
141             }
142         }
143         if (additionalStatesDerivatives != null) {
144             for (final DoubleArrayDictionary.Entry entry : additionalStatesDerivatives.getData()) {
145                 fixedState = fixedState.addAdditionalStateDerivative(entry.getKey(), entry.getValue());
146             }
147         }
148         return fixedState;
149     }
150 
151     /** {@inheritDoc} */
152     public void resetInitialState(final SpacecraftState state) {
153 
154         // ensure the orbit use the specified mu and has no non-Keplerian derivatives
155         final SpacecraftState formerInitial = getInitialState();
156         final double mu = formerInitial == null ? state.getOrbit().getMu() : formerInitial.getOrbit().getMu();
157         final SpacecraftState fixedState = fixState(state.getOrbit(),
158                                                     state.getAttitude(),
159                                                     state.getMass(),
160                                                     mu,
161                                                     state.getAdditionalDataValues(),
162                                                     state.getAdditionalStatesDerivatives());
163 
164         states = new TimeSpanMap<>(fixedState);
165         super.resetInitialState(fixedState);
166 
167     }
168 
169     /** {@inheritDoc} */
170     protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
171         if (forward) {
172             states.addValidAfter(state, state.getDate(), false);
173         } else {
174             states.addValidBefore(state, state.getDate(), false);
175         }
176         stateChanged(state);
177     }
178 
179     /** {@inheritDoc} */
180     public Orbit propagateOrbit(final AbsoluteDate date) {
181 
182         // propagate orbit
183         Orbit orbit = states.get(date).getOrbit();
184         do {
185             // we use a loop here to compensate for very small date shifts error
186             // that occur with long propagation time
187             orbit = orbit.shiftedBy(date.durationFrom(orbit.getDate()));
188         } while (!date.equals(orbit.getDate()));
189 
190         return orbit;
191 
192     }
193 
194     /** {@inheritDoc}*/
195     protected double getMass(final AbsoluteDate date) {
196         return states.get(date).getMass();
197     }
198 
199     /** {@inheritDoc} */
200     @Override
201     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
202                                                         final DoubleArrayDictionary initialJacobianColumns) {
203         // Create the harvester
204         final KeplerianHarvester harvester = new KeplerianHarvester(this, stmName, initialStm, initialJacobianColumns);
205         // Update the list of additional state provider
206         addAdditionalDataProvider(harvester);
207         // Return the configured harvester
208         return harvester;
209     }
210 
211 }