1   /* Copyright 2022-2025 Romain Serra
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.analysis.differentiation.Gradient;
20  import org.hipparchus.analysis.differentiation.GradientField;
21  import org.hipparchus.linear.MatrixUtils;
22  import org.hipparchus.linear.RealMatrix;
23  import org.hipparchus.util.MathArrays;
24  import org.orekit.frames.LOF;
25  import org.orekit.frames.LOFType;
26  import org.orekit.orbits.FieldOrbit;
27  import org.orekit.orbits.Orbit;
28  import org.orekit.orbits.OrbitType;
29  import org.orekit.orbits.PositionAngleBased;
30  import org.orekit.orbits.PositionAngleType;
31  import org.orekit.utils.DerivativeStateUtils;
32  
33  /**
34   * Package private class to map orbital covariance using linearized Keplerian motion
35   * (with non-Keplerian correction from derivatives if available).
36   * The linearization uses the same coordinates as the ones for which the covariance coefficients are defined.
37   *
38   * @author Romain Serra
39   * @since 13.1
40   */
41  class LinearKeplerianCovarianceMapper {
42  
43      /** Default local orbital frame to use. */
44      private static final LOF DEFAULT_LOF = LOFType.QSW;
45  
46      /** Reference orbit. */
47      private final Orbit orbit;
48  
49      /** Reference orbital covariance. */
50      private final StateCovariance stateCovariance;
51  
52      /** Converted covariance (from reference) using the same characteristics than the reference orbit. */
53      private final StateCovariance convertedCovariance;
54  
55      /**
56       * Constructor.
57       * @param orbit initial orbit
58       * @param stateCovariance initial orbital covariance
59       */
60      LinearKeplerianCovarianceMapper(final Orbit orbit, final StateCovariance stateCovariance) {
61          this.orbit = orbit;
62          this.stateCovariance = stateCovariance;
63          this.convertedCovariance = changeCovarianceBefore();
64      }
65  
66      /**
67       * Maps the orbital covariance to the target orbit based on Keplerian linearization.
68       * The coordinates and frame are kept the same as the reference covariance.
69       * @param targetOrbit target orbit
70       * @return mapped covariance
71       */
72      StateCovariance map(final Orbit targetOrbit) {
73          final PositionAngleType positionAngleType = findPositionAngleType();
74          final StateCovariance shiftedCovariance = shiftCovariance(orbit, convertedCovariance, targetOrbit,
75                  positionAngleType);
76          return changeCovarianceAfter(shiftedCovariance, targetOrbit, positionAngleType);
77      }
78  
79      /**
80       * Convert the initial orbital covariance in the same coordinates and frame than the initial orbit.
81       * @return covariance
82       */
83      private StateCovariance changeCovarianceBefore() {
84          final PositionAngleType positionAngleType = findPositionAngleType();
85          final StateCovariance intermediateCovariance = stateCovariance.changeCovarianceFrame(orbit, orbit.getFrame());
86          return intermediateCovariance.changeCovarianceType(orbit, orbit.getType(), positionAngleType);
87      }
88  
89      /**
90       * Extract the position angle type from the initial orbit.
91       * @return position angle type
92       */
93      private PositionAngleType findPositionAngleType() {
94          if (orbit instanceof PositionAngleBased<?>) {
95              final PositionAngleBased<?> positionAngleBased = (PositionAngleBased<?>) orbit;
96              return positionAngleBased.getCachedPositionAngleType();
97          } else {
98              // Cartesian
99              return null;
100         }
101     }
102 
103     /**
104      * Shift orbital covariance according to linearized Keplerian motion, with corrections if non-Keplerian derivatives are present.
105      * @param orbit reference orbit
106      * @param convertedCovariance covariance to shift
107      * @param nextOrbit target orbit
108      * @param positionAngleType position angle type to use
109      * @return shifted covariance
110      */
111     private static StateCovariance shiftCovariance(final Orbit orbit, final StateCovariance convertedCovariance,
112                                                    final Orbit nextOrbit, final PositionAngleType positionAngleType) {
113         final RealMatrix initialCovarianceMatrix = convertedCovariance.getMatrix();
114         final GradientField gradientField = GradientField.getField(6);
115         final FieldOrbit<Gradient> fieldOrbit = DerivativeStateUtils.buildOrbitGradient(gradientField, orbit);
116         final FieldOrbit<Gradient> shiftedFieldOrbit = fieldOrbit.shiftedBy(nextOrbit.durationFrom(orbit));
117         final Gradient[] orbitalArray = MathArrays.buildArray(gradientField, 6);
118         shiftedFieldOrbit.getType().mapOrbitToArray(shiftedFieldOrbit, positionAngleType, orbitalArray, null);
119         final RealMatrix stateTransitionMatrix = MatrixUtils.createRealMatrix(6, 6);
120         for (int i = 0; i < orbitalArray.length; i++) {
121             stateTransitionMatrix.setRow(i, orbitalArray[i].getGradient());
122         }
123         final RealMatrix covarianceMatrix = stateTransitionMatrix.multiply(initialCovarianceMatrix
124                 .multiplyTransposed(stateTransitionMatrix));
125         return new StateCovariance(covarianceMatrix, nextOrbit.getDate(), nextOrbit.getFrame(),
126                 nextOrbit.getType(), positionAngleType);
127     }
128 
129     /**
130      * Convert an orbital covariance to the same coordinates and frame than the initial, reference one.
131      * @param covarianceToConvert covariance to convert
132      * @param nextOrbit target orbit
133      * @param positionAngleType position angle type to use
134      * @return converted covariance
135      */
136     private StateCovariance changeCovarianceAfter(final StateCovariance covarianceToConvert, final Orbit nextOrbit,
137                                                   final PositionAngleType positionAngleType) {
138         final Orbit propagatedOrbit = orbit.shiftedBy(nextOrbit.durationFrom(orbit));
139         final LOF lof = stateCovariance.getLOF() == null ? DEFAULT_LOF : stateCovariance.getLOF();
140         final StateCovariance cartesianCovarianceInFrame = covarianceToConvert.changeCovarianceType(propagatedOrbit,
141                 OrbitType.CARTESIAN, positionAngleType);
142         final StateCovariance covarianceInLof = cartesianCovarianceInFrame.changeCovarianceFrame(propagatedOrbit, lof);
143         if (stateCovariance.getLOF() != null) {
144             return covarianceInLof;
145         } else {
146             // convert back from an arbitrary LOF to reduce approximation
147             final StateCovariance covariance = covarianceInLof.changeCovarianceFrame(nextOrbit, nextOrbit.getFrame());
148             if (stateCovariance.getOrbitType() != OrbitType.CARTESIAN) {
149                 return covariance.changeCovarianceType(propagatedOrbit, stateCovariance.getOrbitType(), positionAngleType);
150             } else {
151                 return covariance;
152             }
153         }
154     }
155 }