IodHerrickGibbs.java

  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. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  20. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  21. import org.hipparchus.util.FastMath;
  22. import org.orekit.errors.OrekitException;
  23. import org.orekit.errors.OrekitMessages;
  24. import org.orekit.estimation.measurements.PV;
  25. import org.orekit.estimation.measurements.Position;
  26. import org.orekit.frames.Frame;
  27. import org.orekit.orbits.CartesianOrbit;
  28. import org.orekit.orbits.FieldCartesianOrbit;
  29. import org.orekit.orbits.FieldOrbit;
  30. import org.orekit.orbits.Orbit;
  31. import org.orekit.time.AbsoluteDate;
  32. import org.orekit.time.FieldAbsoluteDate;
  33. import org.orekit.utils.FieldPVCoordinates;
  34. import org.orekit.utils.PVCoordinates;

  35. /**
  36.  * HerrickGibbs position-based Initial Orbit Determination (IOD) algorithm.
  37.  * <p>
  38.  * An orbit is determined from three position vectors. Because Gibbs IOD algorithm
  39.  * is limited when the position vectors are to close to one other, Herrick-Gibbs
  40.  * IOD algorithm is a variation made to address this limitation.
  41.  * Because this method is only approximate, it is not robust as the Gibbs method
  42.  * for other cases.
  43.  * </p>
  44.  *
  45.  * @see "Vallado, D., Fundamentals of Astrodynamics and Applications, 4th Edition."
  46.  *
  47.  * @author Bryan Cazabonne
  48.  * @since 13.1
  49.  */
  50. public class IodHerrickGibbs {

  51.     /** Gravitational constant. **/
  52.     private final double mu;

  53.     /** Threshold for checking coplanar vectors (Ref: Equation 7-27). */
  54.     private final double COPLANAR_THRESHOLD = FastMath.toRadians(3.);

  55.     /** Constructor.
  56.      * @param mu gravitational constant
  57.      */
  58.     public IodHerrickGibbs(final double mu) {
  59.         this.mu = mu;
  60.     }

  61.     /** Give an initial orbit estimation, assuming Keplerian motion.
  62.      * <p>
  63.      * All observations should be from the same location.
  64.      * </p>
  65.      * @param frame measurements frame, used as output orbit frame
  66.      * @param p1 First position measurement
  67.      * @param p2 Second position measurement
  68.      * @param p3 Third position measurement
  69.      * @return an initial orbit estimation at the central date
  70.      *         (i.e., date of the second position measurement)
  71.      */
  72.     public Orbit estimate(final Frame frame, final Position p1, final Position p2, final Position p3) {
  73.         return estimate(frame, p1.getPosition(), p1.getDate(),
  74.                         p2.getPosition(), p2.getDate(),
  75.                         p3.getPosition(), p3.getDate());
  76.     }

  77.     /** Give an initial orbit estimation, assuming Keplerian motion.
  78.      * <p>
  79.      * All observations should be from the same location.
  80.      * </p>
  81.      * @param frame measurements frame, used as output orbit frame
  82.      * @param pv1 First PV measurement
  83.      * @param pv2 Second PV measurement
  84.      * @param pv3 Third PV measurement
  85.      * @return an initial orbit estimation at the central date
  86.      *         (i.e., date of the second PV measurement)
  87.      */
  88.     public Orbit estimate(final Frame frame, final PV pv1, final PV pv2, final PV pv3) {
  89.         return estimate(frame, pv1.getPosition(), pv1.getDate(),
  90.                         pv2.getPosition(), pv2.getDate(),
  91.                         pv3.getPosition(), pv3.getDate());
  92.     }

  93.     /** Give an initial orbit estimation, assuming Keplerian motion.
  94.      * <p>
  95.      * All observations should be from the same location.
  96.      * </p>
  97.      * @param frame measurements frame, used as output orbit frame
  98.      * @param r1 position vector 1, expressed in frame
  99.      * @param date1 epoch of position vector 1
  100.      * @param r2 position vector 2, expressed in frame
  101.      * @param date2 epoch of position vector 2
  102.      * @param r3 position vector 3, expressed in frame
  103.      * @param date3 epoch of position vector 3
  104.      * @return an initial orbit estimation at the central date
  105.      *         (i.e., date of the second position measurement)
  106.      */
  107.     public Orbit estimate(final Frame frame,
  108.                           final Vector3D r1, final AbsoluteDate date1,
  109.                           final Vector3D r2, final AbsoluteDate date2,
  110.                           final Vector3D r3, final AbsoluteDate date3) {

  111.         // Verify that measurements are not at the same date
  112.         verifyMeasurementEpochs(date1, date2, date3);

  113.         // Get the difference of time between the position vectors
  114.         final double tau32 = date3.getDate().durationFrom(date2.getDate());
  115.         final double tau31 = date3.getDate().durationFrom(date1.getDate());
  116.         final double tau21 = date2.getDate().durationFrom(date1.getDate());

  117.         // Check that measurements are in the same plane
  118.         final double num = r1.normalize().dotProduct(r2.normalize().crossProduct(r3.normalize()));
  119.         if (FastMath.abs(FastMath.PI / 2.0 - FastMath.acos(num)) > COPLANAR_THRESHOLD) {
  120.             throw new OrekitException(OrekitMessages.NON_COPLANAR_POINTS);
  121.         }

  122.         // Compute velocity vector
  123.         final double muOTwelve = mu / 12.0;
  124.         final double coefficient1 = -tau32 * (1.0 / (tau21 * tau31) + muOTwelve / pow3(r1.getNorm()));
  125.         final double coefficient2 = (tau32 - tau21) * (1.0 / (tau21 * tau32) + muOTwelve / pow3(r2.getNorm()));
  126.         final double coefficient3 = tau21 * (1.0 / (tau32 * tau31) + muOTwelve / pow3(r3.getNorm()));
  127.         final Vector3D v2 = r1.scalarMultiply(coefficient1).add(r2.scalarMultiply(coefficient2)).add(r3.scalarMultiply(coefficient3));

  128.         // Convert to an orbit
  129.         return new CartesianOrbit( new PVCoordinates(r2, v2), frame, date2, mu);
  130.     }

  131.     /** Give an initial orbit estimation, assuming Keplerian motion.
  132.      * <p>
  133.      * All observations should be from the same location.
  134.      * </p>
  135.      * @param frame measurements frame, used as output orbit frame
  136.      * @param r1 position vector 1, expressed in frame
  137.      * @param date1 epoch of position vector 1
  138.      * @param r2 position vector 2, expressed in frame
  139.      * @param date2 epoch of position vector 2
  140.      * @param r3 position vector 3, expressed in frame
  141.      * @param date3 epoch of position vector 3
  142.      * @param <T> type of the elements
  143.      * @return an initial orbit estimation at the central date
  144.      *         (i.e., date of the second position measurement)
  145.      */
  146.     public <T extends CalculusFieldElement<T>> FieldOrbit<T> estimate(final Frame frame,
  147.                                                                       final FieldVector3D<T> r1, final FieldAbsoluteDate<T> date1,
  148.                                                                       final FieldVector3D<T> r2, final FieldAbsoluteDate<T> date2,
  149.                                                                       final FieldVector3D<T> r3, final FieldAbsoluteDate<T> date3) {

  150.         // Verify that measurements are not at the same date
  151.         verifyMeasurementEpochs(date1.toAbsoluteDate(), date2.toAbsoluteDate(), date3.toAbsoluteDate());

  152.         // Get the difference of time between the position vectors
  153.         final T tau32 = date3.getDate().durationFrom(date2.getDate());
  154.         final T tau31 = date3.getDate().durationFrom(date1.getDate());
  155.         final T tau21 = date2.getDate().durationFrom(date1.getDate());

  156.         // Check that measurements are in the same plane
  157.         final T num = r1.normalize().dotProduct(r2.normalize().crossProduct(r3.normalize()));
  158.         if (FastMath.abs(FastMath.PI / 2.0 - FastMath.acos(num.getReal())) > COPLANAR_THRESHOLD) {
  159.             throw new OrekitException(OrekitMessages.NON_COPLANAR_POINTS);
  160.         }

  161.         // Compute velocity vector
  162.         final double muOTwelve = mu / 12.0;
  163.         final T coefficient1 = tau32.negate().multiply(tau21.multiply(tau31).reciprocal().add(pow3(r1.getNorm()).reciprocal().multiply(muOTwelve)));
  164.         final T coefficient2 = tau32.subtract(tau21).multiply(tau21.multiply(tau32).reciprocal().add(pow3(r2.getNorm()).reciprocal().multiply(muOTwelve)));
  165.         final T coefficient3 = tau21.multiply(tau32.multiply(tau31).reciprocal().add(pow3(r3.getNorm()).reciprocal().multiply(muOTwelve)));
  166.         final FieldVector3D<T> v2 = r1.scalarMultiply(coefficient1).add(r2.scalarMultiply(coefficient2)).add(r3.scalarMultiply(coefficient3));

  167.         // Convert to an orbit
  168.         return new FieldCartesianOrbit<>( new FieldPVCoordinates<>(r2, v2), frame, date2, date1.getField().getZero().add(mu));
  169.     }

  170.     /** Compute the cubic value.
  171.      * @param value value
  172.      * @return value^3
  173.      */
  174.     private static double pow3(final double value) {
  175.         return value * value * value;
  176.     }

  177.     /** Compute the cubic value.
  178.      * @param value value
  179.      * @param <T> type of the elements
  180.      * @return value^3
  181.      */
  182.     private static <T extends CalculusFieldElement<T>> T pow3(final T value) {
  183.         return value.multiply(value).multiply(value);
  184.     }

  185.     /** Verifies that measurements are not at the same date.
  186.      * @param date1 epoch of position vector 1
  187.      * @param date2 epoch of position vector 2
  188.      * @param date3 epoch of position vector 3
  189.      */
  190.     private void verifyMeasurementEpochs(final AbsoluteDate date1, final AbsoluteDate date2, final AbsoluteDate date3) {
  191.         if (date1.equals(date2) || date1.equals(date3) || date2.equals(date3)) {
  192.             throw new OrekitException(OrekitMessages.NON_DIFFERENT_DATES_FOR_OBSERVATIONS, date1, date2, date3,
  193.                     date2.durationFrom(date1), date3.durationFrom(date1), date3.durationFrom(date2));
  194.         }
  195.     }
  196. }