FieldEllipse.java

  1. /* Copyright 2022-2025 Luc Maisonobe
  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.bodies;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  20. import org.hipparchus.geometry.euclidean.twod.FieldVector2D;
  21. import org.hipparchus.util.FastMath;
  22. import org.hipparchus.util.FieldSinCos;
  23. import org.orekit.frames.Frame;
  24. import org.orekit.utils.TimeStampedFieldPVCoordinates;

  25. /**
  26.  * Model of a 2D ellipse in 3D space.
  27.  * <p>
  28.  * These ellipses are mainly created as plane sections of general 3D ellipsoids,
  29.  * but can be used for other purposes.
  30.  * </p>
  31.  * <p>
  32.  * Instances of this class are guaranteed to be immutable.
  33.  * </p>
  34.  * @see Ellipsoid#getPlaneSection(FieldVector3D, FieldVector3D)
  35.  * @param <T> the type of the field elements
  36.  * @since 12.0
  37.  * @author Luc Maisonobe
  38.  */
  39. public class FieldEllipse<T extends CalculusFieldElement<T>> {

  40.     /** Convergence limit. */
  41.     private static final double ANGULAR_THRESHOLD = 1.0e-12;

  42.     /** Center of the 2D ellipse. */
  43.     private final FieldVector3D<T> center;

  44.     /** Unit vector along the major axis. */
  45.     private final FieldVector3D<T> u;

  46.     /** Unit vector along the minor axis. */
  47.     private final FieldVector3D<T> v;

  48.     /** Semi major axis. */
  49.     private final T a;

  50.     /** Semi minor axis. */
  51.     private final T b;

  52.     /** Frame in which the ellipse is defined. */
  53.     private final Frame frame;

  54.     /** Semi major axis radius power 2. */
  55.     private final T a2;

  56.     /** Semi minor axis power 2. */
  57.     private final T b2;

  58.     /** Eccentricity power 2. */
  59.     private final T e2;

  60.     /** 1 minus flatness. */
  61.     private final T g;

  62.     /** g * g. */
  63.     private final T g2;

  64.     /** Evolute factor along major axis. */
  65.     private final T evoluteFactorX;

  66.     /** Evolute factor along minor axis. */
  67.     private final T evoluteFactorY;

  68.     /** Simple constructor.
  69.      * @param center center of the 2D ellipse
  70.      * @param u unit vector along the major axis
  71.      * @param v unit vector along the minor axis
  72.      * @param a semi major axis
  73.      * @param b semi minor axis
  74.      * @param frame frame in which the ellipse is defined
  75.      */
  76.     public FieldEllipse(final FieldVector3D<T> center, final FieldVector3D<T> u,
  77.                         final FieldVector3D<T> v, final T a, final T b,
  78.                         final Frame frame) {
  79.         this.center = center;
  80.         this.u      = u;
  81.         this.v      = v;
  82.         this.a      = a;
  83.         this.b      = b;
  84.         this.frame  = frame;
  85.         this.a2     = a.square();
  86.         this.g      = b.divide(a);
  87.         this.g2     = g.multiply(g);
  88.         this.e2     = g2.negate().add(1);
  89.         this.b2     = b.square();
  90.         this.evoluteFactorX = a2.subtract(b2).divide(a2.square());
  91.         this.evoluteFactorY = b2.subtract(a2).divide(b2.square());
  92.     }

  93.     /** Get the center of the 2D ellipse.
  94.      * @return center of the 2D ellipse
  95.      */
  96.     public FieldVector3D<T> getCenter() {
  97.         return center;
  98.     }

  99.     /** Get the unit vector along the major axis.
  100.      * @return unit vector along the major axis
  101.      */
  102.     public FieldVector3D<T> getU() {
  103.         return u;
  104.     }

  105.     /** Get the unit vector along the minor axis.
  106.      * @return unit vector along the minor axis
  107.      */
  108.     public FieldVector3D<T> getV() {
  109.         return v;
  110.     }

  111.     /** Get the semi major axis.
  112.      * @return semi major axis
  113.      */
  114.     public T getA() {
  115.         return a;
  116.     }

  117.     /** Get the semi minor axis.
  118.      * @return semi minor axis
  119.      */
  120.     public T getB() {
  121.         return b;
  122.     }

  123.     /** Get the defining frame.
  124.      * @return defining frame
  125.      */
  126.     public Frame getFrame() {
  127.         return frame;
  128.     }

  129.     /** Get a point of the 2D ellipse.
  130.      * @param theta angular parameter on the ellipse (really the eccentric anomaly)
  131.      * @return ellipse point at theta, in underlying ellipsoid frame
  132.      */
  133.     public FieldVector3D<T> pointAt(final T theta) {
  134.         final FieldSinCos<T> scTheta = FastMath.sinCos(theta);
  135.         return toSpace(new FieldVector2D<>(a.multiply(scTheta.cos()), b.multiply(scTheta.sin())));
  136.     }

  137.     /** Create a point from its ellipse-relative coordinates.
  138.      * @param p point defined with respect to ellipse
  139.      * @return point defined with respect to 3D frame
  140.      * @see #toPlane(FieldVector3D)
  141.      */
  142.     public FieldVector3D<T> toSpace(final FieldVector2D<T> p) {
  143.         return new FieldVector3D<>(a.getField().getOne(), center, p.getX(), u, p.getY(), v);
  144.     }

  145.     /** Project a point to the ellipse plane.
  146.      * @param p point defined with respect to 3D frame
  147.      * @return point defined with respect to ellipse
  148.      * @see #toSpace(FieldVector2D)
  149.      */
  150.     public FieldVector2D<T> toPlane(final FieldVector3D<T> p) {
  151.         final FieldVector3D<T> delta = p.subtract(center);
  152.         return new FieldVector2D<>(FieldVector3D.dotProduct(delta, u), FieldVector3D.dotProduct(delta, v));
  153.     }

  154.     /** Find the closest ellipse point.
  155.      * @param p point in the ellipse plane to project on the ellipse itself
  156.      * @return closest point belonging to 2D meridian ellipse
  157.      */
  158.     public FieldVector2D<T> projectToEllipse(final FieldVector2D<T> p) {

  159.         final T x = FastMath.abs(p.getX());
  160.         final T y = p.getY();

  161.         if (x.getReal() <= ANGULAR_THRESHOLD * FastMath.abs(y.getReal())) {
  162.             // the point is almost on the minor axis, approximate the ellipse with
  163.             // the osculating circle whose center is at evolute cusp along minor axis
  164.             final T osculatingRadius = a2.divide(b);
  165.             final T evoluteCuspZ     = FastMath.copySign(a.multiply(e2).divide(g), y.negate());
  166.             final T deltaZ           = y.subtract(evoluteCuspZ);
  167.             final T ratio            = osculatingRadius.divide(FastMath.hypot(deltaZ, x));
  168.             return new FieldVector2D<>(FastMath.copySign(ratio.multiply(x), p.getX()),
  169.                                        evoluteCuspZ.add(ratio.multiply(deltaZ)));
  170.         }

  171.         if (FastMath.abs(y.getReal()) <= ANGULAR_THRESHOLD * x.getReal()) {
  172.             // the point is almost on the major axis

  173.             final T osculatingRadius = b2.divide(a);
  174.             final T evoluteCuspR     = a.multiply(e2);
  175.             final T deltaR           = x.subtract(evoluteCuspR);
  176.             if (deltaR.getReal() >= 0) {
  177.                 // the point is outside of the ellipse evolute, approximate the ellipse
  178.                 // with the osculating circle whose center is at evolute cusp along major axis
  179.                 final T ratio = osculatingRadius.divide(FastMath.hypot(y, deltaR));
  180.                 return new FieldVector2D<>(FastMath.copySign(evoluteCuspR.add(ratio.multiply(deltaR)), p.getX()),
  181.                                            ratio.multiply(y));
  182.             }

  183.             // the point is on the part of the major axis within ellipse evolute
  184.             // we can compute the closest ellipse point analytically
  185.             final T rEllipse = x.divide(e2);
  186.             return new FieldVector2D<>(FastMath.copySign(rEllipse, p.getX()),
  187.                                        FastMath.copySign(g.multiply(FastMath.sqrt(a2.subtract(rEllipse.multiply(rEllipse)))), y));

  188.         } else {

  189.             // initial point at evolute cusp along major axis
  190.             T omegaX = a.multiply(e2);
  191.             T omegaY = a.getField().getZero();

  192.             T projectedX  = x;
  193.             T projectedY  = y;
  194.             double deltaX = Double.POSITIVE_INFINITY;
  195.             double deltaY = Double.POSITIVE_INFINITY;
  196.             int count = 0;
  197.             final double threshold = ANGULAR_THRESHOLD * ANGULAR_THRESHOLD * a2.getReal();
  198.             while ((deltaX * deltaX + deltaY * deltaY) > threshold && count++ < 100) { // this loop usually converges in 3 iterations

  199.                 // find point at the intersection of ellipse and line going from query point to evolute point
  200.                 final T dx         = x.subtract(omegaX);
  201.                 final T dy         = y.subtract(omegaY);
  202.                 final T alpha      = b2.multiply(dx).multiply(dx).add(a2.multiply(dy).multiply(dy));
  203.                 final T betaPrime  = b2.multiply(omegaX).multiply(dx).add(a2.multiply(omegaY).multiply(dy));
  204.                 final T gamma      = b2.multiply(omegaX).multiply(omegaX).add(a2.multiply(omegaY).multiply(omegaY)).subtract(a2.multiply(b2));
  205.                 final T deltaPrime = a.linearCombination(betaPrime, betaPrime, alpha.negate(), gamma);
  206.                 final T ratio      = (betaPrime.getReal() <= 0) ?
  207.                                           FastMath.sqrt(deltaPrime).subtract(betaPrime).divide(alpha) :
  208.                                           gamma.negate().divide(FastMath.sqrt(deltaPrime).add(betaPrime));
  209.                 final T previousX  = projectedX;
  210.                 final T previousY  = projectedY;
  211.                 projectedX = omegaX.add(ratio.multiply(dx));
  212.                 projectedY = omegaY.add(ratio.multiply(dy));

  213.                 // find new evolute point
  214.                 omegaX     = evoluteFactorX.multiply(projectedX).multiply(projectedX).multiply(projectedX);
  215.                 omegaY     = evoluteFactorY.multiply(projectedY).multiply(projectedY).multiply(projectedY);

  216.                 // compute convergence parameters
  217.                 deltaX     = projectedX.subtract(previousX).getReal();
  218.                 deltaY     = projectedY.subtract(previousY).getReal();

  219.             }
  220.             return new FieldVector2D<>(FastMath.copySign(projectedX, p.getX()), projectedY);
  221.         }
  222.     }

  223.     /** Project position-velocity-acceleration on an ellipse.
  224.      * @param pv position-velocity-acceleration to project, in the reference frame
  225.      * @return projected position-velocity-acceleration
  226.      */
  227.     public TimeStampedFieldPVCoordinates<T> projectToEllipse(final TimeStampedFieldPVCoordinates<T> pv) {

  228.         // find the closest point in the meridian plane
  229.         final FieldVector2D<T>p2D = toPlane(pv.getPosition());
  230.         final FieldVector2D<T>e2D = projectToEllipse(p2D);

  231.         // tangent to the ellipse
  232.         final T fx = a2.negate().multiply(e2D.getY());
  233.         final T fy = b2.multiply(e2D.getX());
  234.         final T f2 = fx.square().add(fy.square());
  235.         final T f  = FastMath.sqrt(f2);
  236.         final FieldVector2D<T>tangent = new FieldVector2D<>(fx.divide(f), fy.divide(f));

  237.         // normal to the ellipse (towards interior)
  238.         final FieldVector2D<T>normal = new FieldVector2D<>(tangent.getY().negate(), tangent.getX());

  239.         // center of curvature
  240.         final T x2     = e2D.getX().multiply(e2D.getX());
  241.         final T y2     = e2D.getY().multiply(e2D.getY());
  242.         final T eX     = evoluteFactorX.multiply(x2);
  243.         final T eY     = evoluteFactorY.multiply(y2);
  244.         final T omegaX = eX.multiply(e2D.getX());
  245.         final T omegaY = eY.multiply(e2D.getY());

  246.         // velocity projection ratio
  247.         final T rho                = FastMath.hypot(e2D.getX().subtract(omegaX), e2D.getY().subtract(omegaY));
  248.         final T d                  = FastMath.hypot(p2D.getX().subtract(omegaX), p2D.getY().subtract(omegaY));
  249.         final T projectionRatio    = rho.divide(d);

  250.         // tangential velocity
  251.         final FieldVector2D<T>pDot2D     = new FieldVector2D<>(FieldVector3D.dotProduct(pv.getVelocity(), u),
  252.                                                               FieldVector3D.dotProduct(pv.getVelocity(), v));
  253.         final T   pDotTangent            = pDot2D.dotProduct(tangent);
  254.         final T   pDotNormal             = pDot2D.dotProduct(normal);
  255.         final T   eDotTangent            = projectionRatio.multiply(pDotTangent);
  256.         final FieldVector2D<T>eDot2D     = new FieldVector2D<>(eDotTangent, tangent);
  257.         final FieldVector2D<T>tangentDot = new FieldVector2D<>(a2.multiply(b2).
  258.                                                                multiply(e2D.getX().multiply(eDot2D.getY()).
  259.                                                                         subtract(e2D.getY().multiply(eDot2D.getX()))).
  260.                                                                divide(f2),
  261.                                                                normal);

  262.         // velocity of the center of curvature in the meridian plane
  263.         final T omegaXDot          = eX.multiply(eDotTangent).multiply(tangent.getX()).multiply(3);
  264.         final T omegaYDot          = eY.multiply(eDotTangent).multiply(tangent.getY()).multiply(3);

  265.         // derivative of the projection ratio
  266.         final T voz                = omegaXDot.multiply(tangent.getY()).subtract(omegaYDot.multiply(tangent.getX()));
  267.         final T vsz                = pDotNormal.negate();
  268.         final T projectionRatioDot = rho.subtract(d).multiply(voz).subtract(rho.multiply(vsz)).divide(d.multiply(d));

  269.         // acceleration
  270.         final FieldVector2D<T>pDotDot2D  = new FieldVector2D<>(FieldVector3D.dotProduct(pv.getAcceleration(), u),
  271.                                                                FieldVector3D.dotProduct(pv.getAcceleration(), v));
  272.         final T   pDotDotTangent         = pDotDot2D.dotProduct(tangent);
  273.         final T   pDotTangentDot         = pDot2D.dotProduct(tangentDot);
  274.         final T   eDotDotTangent         = projectionRatio.multiply(pDotDotTangent.add(pDotTangentDot)).
  275.                                            add(projectionRatioDot.multiply(pDotTangent));
  276.         final FieldVector2D<T>eDotDot2D = new FieldVector2D<>(eDotDotTangent, tangent, eDotTangent, tangentDot);

  277.         // back to 3D
  278.         final FieldVector3D<T> e3D       = toSpace(e2D);
  279.         final FieldVector3D<T> eDot3D    = new FieldVector3D<>(eDot2D.getX(), u, eDot2D.getY(), v);
  280.         final FieldVector3D<T> eDotDot3D = new FieldVector3D<>(eDotDot2D.getX(), u, eDotDot2D.getY(), v);

  281.         return new TimeStampedFieldPVCoordinates<>(pv.getDate(), e3D, eDot3D, eDotDot3D);

  282.     }

  283.     /** Find the center of curvature (point on the evolute) at the nadir of a point.
  284.      * @param point point in the ellipse plane
  285.      * @return center of curvature of the ellipse directly at point nadir
  286.      */
  287.     public FieldVector2D<T> getCenterOfCurvature(final FieldVector2D<T> point) {
  288.         final FieldVector2D<T>projected = projectToEllipse(point);
  289.         return new FieldVector2D<>(evoluteFactorX.multiply(projected.getX()).multiply(projected.getX()).multiply(projected.getX()),
  290.                                    evoluteFactorY.multiply(projected.getY()).multiply(projected.getY()).multiply(projected.getY()));
  291.     }

  292. }