1 /* Copyright 2022-2025 Bryan Cazabonne
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 * Bryan Cazabonne 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.estimation.iod;
18
19 import org.hipparchus.CalculusFieldElement;
20 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
21 import org.hipparchus.geometry.euclidean.threed.Vector3D;
22 import org.hipparchus.util.FastMath;
23 import org.orekit.errors.OrekitException;
24 import org.orekit.errors.OrekitMessages;
25 import org.orekit.estimation.measurements.PV;
26 import org.orekit.estimation.measurements.Position;
27 import org.orekit.frames.Frame;
28 import org.orekit.orbits.CartesianOrbit;
29 import org.orekit.orbits.FieldCartesianOrbit;
30 import org.orekit.orbits.FieldOrbit;
31 import org.orekit.orbits.Orbit;
32 import org.orekit.time.AbsoluteDate;
33 import org.orekit.time.FieldAbsoluteDate;
34 import org.orekit.utils.FieldPVCoordinates;
35 import org.orekit.utils.PVCoordinates;
36
37 /**
38 * HerrickGibbs position-based Initial Orbit Determination (IOD) algorithm.
39 * <p>
40 * An orbit is determined from three position vectors. Because Gibbs IOD algorithm
41 * is limited when the position vectors are to close to one other, Herrick-Gibbs
42 * IOD algorithm is a variation made to address this limitation.
43 * Because this method is only approximate, it is not robust as the Gibbs method
44 * for other cases.
45 * </p>
46 *
47 * @see "Vallado, D., Fundamentals of Astrodynamics and Applications, 4th Edition."
48 *
49 * @author Bryan Cazabonne
50 * @since 13.1
51 */
52 public class IodHerrickGibbs {
53
54 /** Gravitational constant. **/
55 private final double mu;
56
57 /** Threshold for checking coplanar vectors (Ref: Equation 7-27). */
58 private final double COPLANAR_THRESHOLD = FastMath.toRadians(3.);
59
60 /** Constructor.
61 * @param mu gravitational constant
62 */
63 public IodHerrickGibbs(final double mu) {
64 this.mu = mu;
65 }
66
67 /** Give an initial orbit estimation, assuming Keplerian motion.
68 * <p>
69 * All observations should be from the same location.
70 * </p>
71 * @param frame measurements frame, used as output orbit frame
72 * @param p1 First position measurement
73 * @param p2 Second position measurement
74 * @param p3 Third position measurement
75 * @return an initial orbit estimation at the central date
76 * (i.e., date of the second position measurement)
77 */
78 public Orbit estimate(final Frame frame, final Position p1, final Position p2, final Position p3) {
79 return estimate(frame, p1.getPosition(), p1.getDate(),
80 p2.getPosition(), p2.getDate(),
81 p3.getPosition(), p3.getDate());
82 }
83
84 /** Give an initial orbit estimation, assuming Keplerian motion.
85 * <p>
86 * All observations should be from the same location.
87 * </p>
88 * @param frame measurements frame, used as output orbit frame
89 * @param pv1 First PV measurement
90 * @param pv2 Second PV measurement
91 * @param pv3 Third PV measurement
92 * @return an initial orbit estimation at the central date
93 * (i.e., date of the second PV measurement)
94 */
95 public Orbit estimate(final Frame frame, final PV pv1, final PV pv2, final PV pv3) {
96 return estimate(frame, pv1.getPosition(), pv1.getDate(),
97 pv2.getPosition(), pv2.getDate(),
98 pv3.getPosition(), pv3.getDate());
99 }
100
101 /** Give an initial orbit estimation, assuming Keplerian motion.
102 * <p>
103 * All observations should be from the same location.
104 * </p>
105 * @param frame measurements frame, used as output orbit frame
106 * @param r1 position vector 1, expressed in frame
107 * @param date1 epoch of position vector 1
108 * @param r2 position vector 2, expressed in frame
109 * @param date2 epoch of position vector 2
110 * @param r3 position vector 3, expressed in frame
111 * @param date3 epoch of position vector 3
112 * @return an initial orbit estimation at the central date
113 * (i.e., date of the second position measurement)
114 */
115 public Orbit estimate(final Frame frame,
116 final Vector3D r1, final AbsoluteDate date1,
117 final Vector3D r2, final AbsoluteDate date2,
118 final Vector3D r3, final AbsoluteDate date3) {
119
120 // Verify that measurements are not at the same date
121 verifyMeasurementEpochs(date1, date2, date3);
122
123 // Get the difference of time between the position vectors
124 final double tau32 = date3.getDate().durationFrom(date2.getDate());
125 final double tau31 = date3.getDate().durationFrom(date1.getDate());
126 final double tau21 = date2.getDate().durationFrom(date1.getDate());
127
128 // Check that measurements are in the same plane
129 final double num = r1.normalize().dotProduct(r2.normalize().crossProduct(r3.normalize()));
130 if (FastMath.abs(FastMath.PI / 2.0 - FastMath.acos(num)) > COPLANAR_THRESHOLD) {
131 throw new OrekitException(OrekitMessages.NON_COPLANAR_POINTS);
132 }
133
134 // Compute velocity vector
135 final double muOTwelve = mu / 12.0;
136 final double coefficient1 = -tau32 * (1.0 / (tau21 * tau31) + muOTwelve / pow3(r1.getNorm()));
137 final double coefficient2 = (tau32 - tau21) * (1.0 / (tau21 * tau32) + muOTwelve / pow3(r2.getNorm()));
138 final double coefficient3 = tau21 * (1.0 / (tau32 * tau31) + muOTwelve / pow3(r3.getNorm()));
139 final Vector3D v2 = r1.scalarMultiply(coefficient1).add(r2.scalarMultiply(coefficient2)).add(r3.scalarMultiply(coefficient3));
140
141 // Convert to an orbit
142 return new CartesianOrbit( new PVCoordinates(r2, v2), frame, date2, mu);
143 }
144
145 /** Give an initial orbit estimation, assuming Keplerian motion.
146 * <p>
147 * All observations should be from the same location.
148 * </p>
149 * @param frame measurements frame, used as output orbit frame
150 * @param r1 position vector 1, expressed in frame
151 * @param date1 epoch of position vector 1
152 * @param r2 position vector 2, expressed in frame
153 * @param date2 epoch of position vector 2
154 * @param r3 position vector 3, expressed in frame
155 * @param date3 epoch of position vector 3
156 * @param <T> type of the elements
157 * @return an initial orbit estimation at the central date
158 * (i.e., date of the second position measurement)
159 */
160 public <T extends CalculusFieldElement<T>> FieldOrbit<T> estimate(final Frame frame,
161 final FieldVector3D<T> r1, final FieldAbsoluteDate<T> date1,
162 final FieldVector3D<T> r2, final FieldAbsoluteDate<T> date2,
163 final FieldVector3D<T> r3, final FieldAbsoluteDate<T> date3) {
164
165 // Verify that measurements are not at the same date
166 verifyMeasurementEpochs(date1.toAbsoluteDate(), date2.toAbsoluteDate(), date3.toAbsoluteDate());
167
168 // Get the difference of time between the position vectors
169 final T tau32 = date3.getDate().durationFrom(date2.getDate());
170 final T tau31 = date3.getDate().durationFrom(date1.getDate());
171 final T tau21 = date2.getDate().durationFrom(date1.getDate());
172
173 // Check that measurements are in the same plane
174 final T num = r1.normalize().dotProduct(r2.normalize().crossProduct(r3.normalize()));
175 if (FastMath.abs(FastMath.PI / 2.0 - FastMath.acos(num.getReal())) > COPLANAR_THRESHOLD) {
176 throw new OrekitException(OrekitMessages.NON_COPLANAR_POINTS);
177 }
178
179 // Compute velocity vector
180 final double muOTwelve = mu / 12.0;
181 final T coefficient1 = tau32.negate().multiply(tau21.multiply(tau31).reciprocal().add(pow3(r1.getNorm()).reciprocal().multiply(muOTwelve)));
182 final T coefficient2 = tau32.subtract(tau21).multiply(tau21.multiply(tau32).reciprocal().add(pow3(r2.getNorm()).reciprocal().multiply(muOTwelve)));
183 final T coefficient3 = tau21.multiply(tau32.multiply(tau31).reciprocal().add(pow3(r3.getNorm()).reciprocal().multiply(muOTwelve)));
184 final FieldVector3D<T> v2 = r1.scalarMultiply(coefficient1).add(r2.scalarMultiply(coefficient2)).add(r3.scalarMultiply(coefficient3));
185
186 // Convert to an orbit
187 return new FieldCartesianOrbit<>( new FieldPVCoordinates<>(r2, v2), frame, date2, date1.getField().getZero().add(mu));
188 }
189
190 /** Compute the cubic value.
191 * @param value value
192 * @return value^3
193 */
194 private static double pow3(final double value) {
195 return value * value * value;
196 }
197
198 /** Compute the cubic value.
199 * @param value value
200 * @param <T> type of the elements
201 * @return value^3
202 */
203 private static <T extends CalculusFieldElement<T>> T pow3(final T value) {
204 return value.multiply(value).multiply(value);
205 }
206
207 /** Verifies that measurements are not at the same date.
208 * @param date1 epoch of position vector 1
209 * @param date2 epoch of position vector 2
210 * @param date3 epoch of position vector 3
211 */
212 private void verifyMeasurementEpochs(final AbsoluteDate date1, final AbsoluteDate date2, final AbsoluteDate date3) {
213 if (date1.equals(date2) || date1.equals(date3) || date2.equals(date3)) {
214 throw new OrekitException(OrekitMessages.NON_DIFFERENT_DATES_FOR_OBSERVATIONS, date1, date2, date3,
215 date2.durationFrom(date1), date3.durationFrom(date1), date3.durationFrom(date2));
216 }
217 }
218 }