OneAxisEllipsoid.java

  1. /* Copyright 2002-2019 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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 java.io.Serializable;

  19. import org.hipparchus.RealFieldElement;
  20. import org.hipparchus.analysis.differentiation.DerivativeStructure;
  21. import org.hipparchus.geometry.euclidean.threed.FieldLine;
  22. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  23. import org.hipparchus.geometry.euclidean.threed.Line;
  24. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  25. import org.hipparchus.geometry.euclidean.twod.Vector2D;
  26. import org.hipparchus.util.FastMath;
  27. import org.hipparchus.util.MathArrays;
  28. import org.orekit.frames.FieldTransform;
  29. import org.orekit.frames.Frame;
  30. import org.orekit.frames.Transform;
  31. import org.orekit.time.AbsoluteDate;
  32. import org.orekit.time.FieldAbsoluteDate;
  33. import org.orekit.utils.PVCoordinates;
  34. import org.orekit.utils.TimeStampedPVCoordinates;


  35. /** Modeling of a one-axis ellipsoid.

  36.  * <p>One-axis ellipsoids is a good approximate model for most planet-size
  37.  * and larger natural bodies. It is the equilibrium shape reached by
  38.  * a fluid body under its own gravity field when it rotates. The symmetry
  39.  * axis is the rotation or polar axis.</p>

  40.  * @author Luc Maisonobe
  41.  */
  42. public class OneAxisEllipsoid extends Ellipsoid implements BodyShape {

  43.     /** Serializable UID. */
  44.     private static final long serialVersionUID = 20130518L;

  45.     /** Threshold for polar and equatorial points detection. */
  46.     private static final double ANGULAR_THRESHOLD = 1.0e-4;

  47.     /** Body frame related to body shape. */
  48.     private final Frame bodyFrame;

  49.     /** Equatorial radius power 2. */
  50.     private final double ae2;

  51.     /** Polar radius power 2. */
  52.     private final double ap2;

  53.     /** Flattening. */
  54.     private final double f;

  55.     /** Eccentricity power 2. */
  56.     private final double e2;

  57.     /** 1 minus flatness. */
  58.     private final double g;

  59.     /** g * g. */
  60.     private final double g2;

  61.     /** Convergence limit. */
  62.     private double angularThreshold;

  63.     /** Simple constructor.
  64.      * <p>Standard values for Earth models can be found in the {@link org.orekit.utils.Constants Constants} class:</p>
  65.      * <table border="1" cellpadding="5" style="background-color:#f5f5dc;" summary="">
  66.      * <caption>Ellipsoid Models</caption>
  67.      * <tr style="background-color:#c9d5c9;"><th>model</th><th>a<sub>e</sub> (m)</th> <th>f</th></tr>
  68.      * <tr><td style="background-color:#c9d5c9;">GRS 80</td>
  69.      *     <td>{@link org.orekit.utils.Constants#GRS80_EARTH_EQUATORIAL_RADIUS Constants.GRS80_EARTH_EQUATORIAL_RADIUS}</td>
  70.      *     <td>{@link org.orekit.utils.Constants#GRS80_EARTH_FLATTENING Constants.GRS80_EARTH_FLATTENING}</td></tr>
  71.      * <tr><td style="background-color:#c9d5c9;">WGS84</td>
  72.      *     <td>{@link org.orekit.utils.Constants#WGS84_EARTH_EQUATORIAL_RADIUS Constants.WGS84_EARTH_EQUATORIAL_RADIUS}</td>
  73.      *     <td>{@link org.orekit.utils.Constants#WGS84_EARTH_FLATTENING Constants.WGS84_EARTH_FLATTENING}</td></tr>
  74.      * </table>
  75.      * @param ae equatorial radius
  76.      * @param f the flattening (f = (a-b)/a)
  77.      * @param bodyFrame body frame related to body shape
  78.      * @see org.orekit.frames.FramesFactory#getITRF(org.orekit.utils.IERSConventions, boolean)
  79.      */
  80.     public OneAxisEllipsoid(final double ae, final double f,
  81.                             final Frame bodyFrame) {
  82.         super(bodyFrame, ae, ae, ae * (1.0 - f));
  83.         this.f    = f;
  84.         this.ae2  = ae * ae;
  85.         this.e2   = f * (2.0 - f);
  86.         this.g    = 1.0 - f;
  87.         this.g2   = g * g;
  88.         this.ap2  = ae2 * g2;
  89.         setAngularThreshold(1.0e-12);
  90.         this.bodyFrame = bodyFrame;
  91.     }

  92.     /** Set the angular convergence threshold.
  93.      * <p>The angular threshold is used both to identify points close to
  94.      * the ellipse axes and as the convergence threshold used to
  95.      * stop the iterations in the {@link #transform(Vector3D, Frame,
  96.      * AbsoluteDate)} method.</p>
  97.      * <p>If this method is not called, the default value is set to
  98.      * 10<sup>-12</sup>.</p>
  99.      * @param angularThreshold angular convergence threshold (rad)
  100.      */
  101.     public void setAngularThreshold(final double angularThreshold) {
  102.         this.angularThreshold = angularThreshold;
  103.     }

  104.     /** Get the equatorial radius of the body.
  105.      * @return equatorial radius of the body (m)
  106.      */
  107.     public double getEquatorialRadius() {
  108.         return getA();
  109.     }

  110.     /** Get the flattening of the body: f = (a-b)/a.
  111.      * @return the flattening
  112.      */
  113.     public double getFlattening() {
  114.         return f;
  115.     }

  116.     /** {@inheritDoc} */
  117.     public Frame getBodyFrame() {
  118.         return bodyFrame;
  119.     }

  120.     /** Get the intersection point of a line with the surface of the body.
  121.      * <p>A line may have several intersection points with a closed
  122.      * surface (we consider the one point case as a degenerated two
  123.      * points case). The close parameter is used to select which of
  124.      * these points should be returned. The selected point is the one
  125.      * that is closest to the close point.</p>
  126.      * @param line test line (may intersect the body or not)
  127.      * @param close point used for intersections selection
  128.      * @param frame frame in which line is expressed
  129.      * @param date date of the line in given frame
  130.      * @return intersection point at altitude zero or null if the line does
  131.      * not intersect the surface
  132.      * @since 9.3
  133.      */
  134.     public Vector3D getCartesianIntersectionPoint(final Line line, final Vector3D close,
  135.                                                   final Frame frame, final AbsoluteDate date) {

  136.         // transform line and close to body frame
  137.         final Transform frameToBodyFrame = frame.getTransformTo(bodyFrame, date);
  138.         final Line lineInBodyFrame = frameToBodyFrame.transformLine(line);

  139.         // compute some miscellaneous variables
  140.         final Vector3D point    = lineInBodyFrame.getOrigin();
  141.         final double x          = point.getX();
  142.         final double y          = point.getY();
  143.         final double z          = point.getZ();
  144.         final double z2         = z * z;
  145.         final double r2         = x * x + y * y;

  146.         final Vector3D direction = lineInBodyFrame.getDirection();
  147.         final double dx         = direction.getX();
  148.         final double dy         = direction.getY();
  149.         final double dz         = direction.getZ();
  150.         final double cz2        = dx * dx + dy * dy;

  151.         // abscissa of the intersection as a root of a 2nd degree polynomial :
  152.         // a k^2 - 2 b k + c = 0
  153.         final double a  = 1.0 - e2 * cz2;
  154.         final double b  = -(g2 * (x * dx + y * dy) + z * dz);
  155.         final double c  = g2 * (r2 - ae2) + z2;
  156.         final double b2 = b * b;
  157.         final double ac = a * c;
  158.         if (b2 < ac) {
  159.             return null;
  160.         }
  161.         final double s  = FastMath.sqrt(b2 - ac);
  162.         final double k1 = (b < 0) ? (b - s) / a : c / (b + s);
  163.         final double k2 = c / (a * k1);

  164.         // select the right point
  165.         final Vector3D closeInBodyFrame = frameToBodyFrame.transformPosition(close);
  166.         final double   closeAbscissa    = lineInBodyFrame.getAbscissa(closeInBodyFrame);
  167.         final double k =
  168.             (FastMath.abs(k1 - closeAbscissa) < FastMath.abs(k2 - closeAbscissa)) ? k1 : k2;
  169.         return lineInBodyFrame.pointAt(k);

  170.     }

  171.     /** {@inheritDoc} */
  172.     public GeodeticPoint getIntersectionPoint(final Line line, final Vector3D close,
  173.                                               final Frame frame, final AbsoluteDate date) {

  174.         final Vector3D intersection = getCartesianIntersectionPoint(line, close, frame, date);
  175.         if (intersection == null) {
  176.             return null;
  177.         }
  178.         final double ix = intersection.getX();
  179.         final double iy = intersection.getY();
  180.         final double iz = intersection.getZ();

  181.         final double lambda = FastMath.atan2(iy, ix);
  182.         final double phi    = FastMath.atan2(iz, g2 * FastMath.sqrt(ix * ix + iy * iy));
  183.         return new GeodeticPoint(phi, lambda, 0.0);

  184.     }

  185.     /** Get the intersection point of a line with the surface of the body.
  186.      * <p>A line may have several intersection points with a closed
  187.      * surface (we consider the one point case as a degenerated two
  188.      * points case). The close parameter is used to select which of
  189.      * these points should be returned. The selected point is the one
  190.      * that is closest to the close point.</p>
  191.      * @param line test line (may intersect the body or not)
  192.      * @param close point used for intersections selection
  193.      * @param frame frame in which line is expressed
  194.      * @param date date of the line in given frame
  195.      * @param <T> type of the field elements
  196.      * @return intersection point at altitude zero or null if the line does
  197.      * not intersect the surface
  198.      * @since 9.3
  199.      */
  200.     public <T extends RealFieldElement<T>> FieldVector3D<T> getCartesianIntersectionPoint(final FieldLine<T> line,
  201.                                                                                           final FieldVector3D<T> close,
  202.                                                                                           final Frame frame,
  203.                                                                                           final FieldAbsoluteDate<T> date) {

  204.         // transform line and close to body frame
  205.         final FieldTransform<T> frameToBodyFrame = frame.getTransformTo(bodyFrame, date);
  206.         final FieldLine<T>      lineInBodyFrame  = frameToBodyFrame.transformLine(line);

  207.         // compute some miscellaneous variables
  208.         final FieldVector3D<T> point = lineInBodyFrame.getOrigin();
  209.         final T x  = point.getX();
  210.         final T y  = point.getY();
  211.         final T z  = point.getZ();
  212.         final T z2 = z.multiply(z);
  213.         final T r2 = x.multiply(x).add(y.multiply(y));

  214.         final FieldVector3D<T> direction = lineInBodyFrame.getDirection();
  215.         final T dx  = direction.getX();
  216.         final T dy  = direction.getY();
  217.         final T dz  = direction.getZ();
  218.         final T cz2 = dx.multiply(dx).add(dy.multiply(dy));

  219.         // abscissa of the intersection as a root of a 2nd degree polynomial :
  220.         // a k^2 - 2 b k + c = 0
  221.         final T a  = cz2.multiply(e2).subtract(1.0).negate();
  222.         final T b  = x.multiply(dx).add(y.multiply(dy)).multiply(g2).add(z.multiply(dz)).negate();
  223.         final T c  = r2.subtract(ae2).multiply(g2).add(z2);
  224.         final T b2 = b.multiply(b);
  225.         final T ac = a.multiply(c);
  226.         if (b2.getReal() < ac.getReal()) {
  227.             return null;
  228.         }
  229.         final T s  = b2.subtract(ac).sqrt();
  230.         final T k1 = (b.getReal() < 0) ? b.subtract(s).divide(a) : c.divide(b.add(s));
  231.         final T k2 = c.divide(a.multiply(k1));

  232.         // select the right point
  233.         final FieldVector3D<T>  closeInBodyFrame = frameToBodyFrame.transformPosition(close);
  234.         final T                 closeAbscissa    = lineInBodyFrame.getAbscissa(closeInBodyFrame);
  235.         final T k = (FastMath.abs(k1.getReal() - closeAbscissa.getReal()) < FastMath.abs(k2.getReal() - closeAbscissa.getReal())) ?
  236.                     k1 : k2;
  237.         return lineInBodyFrame.pointAt(k);
  238.     }

  239.     /** {@inheritDoc} */
  240.     public <T extends RealFieldElement<T>> FieldGeodeticPoint<T> getIntersectionPoint(final FieldLine<T> line,
  241.                                                                                       final FieldVector3D<T> close,
  242.                                                                                       final Frame frame,
  243.                                                                                       final FieldAbsoluteDate<T> date) {

  244.         final FieldVector3D<T> intersection = getCartesianIntersectionPoint(line, close, frame, date);
  245.         if (intersection == null) {
  246.             return null;
  247.         }
  248.         final T ix = intersection.getX();
  249.         final T iy = intersection.getY();
  250.         final T iz = intersection.getZ();

  251.         final T lambda = iy.atan2(ix);
  252.         final T phi    = iz.atan2(ix.multiply(ix).add(iy.multiply(iy)).sqrt().multiply(g2));
  253.         return new FieldGeodeticPoint<>(phi, lambda, phi.getField().getZero());

  254.     }

  255.     /** {@inheritDoc} */
  256.     public Vector3D transform(final GeodeticPoint point) {
  257.         final double longitude = point.getLongitude();
  258.         final double cLambda   = FastMath.cos(longitude);
  259.         final double sLambda   = FastMath.sin(longitude);
  260.         final double latitude  = point.getLatitude();
  261.         final double cPhi      = FastMath.cos(latitude);
  262.         final double sPhi      = FastMath.sin(latitude);
  263.         final double h         = point.getAltitude();
  264.         final double n         = getA() / FastMath.sqrt(1.0 - e2 * sPhi * sPhi);
  265.         final double r         = (n + h) * cPhi;
  266.         return new Vector3D(r * cLambda, r * sLambda, (g2 * n + h) * sPhi);
  267.     }

  268.     /** {@inheritDoc} */
  269.     public <T extends RealFieldElement<T>> FieldVector3D<T> transform(final FieldGeodeticPoint<T> point) {

  270.         final T latitude  = point.getLatitude();
  271.         final T longitude = point.getLongitude();
  272.         final T altitude  = point.getAltitude();

  273.         final T cLambda = longitude.cos();
  274.         final T sLambda = longitude.sin();
  275.         final T cPhi    = latitude.cos();
  276.         final T sPhi    = latitude.sin();
  277.         final T n       = sPhi.multiply(sPhi).multiply(e2).subtract(1.0).negate().sqrt().reciprocal().multiply(getA());
  278.         final T r       = n.add(altitude).multiply(cPhi);

  279.         return new FieldVector3D<>(r.multiply(cLambda),
  280.                                    r.multiply(sLambda),
  281.                                    sPhi.multiply(altitude.add(n.multiply(g2))));
  282.     }

  283.     /** {@inheritDoc} */
  284.     public Vector3D projectToGround(final Vector3D point, final AbsoluteDate date, final Frame frame) {

  285.         // transform point to body frame
  286.         final Transform  toBody    = frame.getTransformTo(bodyFrame, date);
  287.         final Vector3D   p         = toBody.transformPosition(point);
  288.         final double     z         = p.getZ();
  289.         final double     r         = FastMath.hypot(p.getX(), p.getY());

  290.         // set up the 2D meridian ellipse
  291.         final Ellipse meridian = new Ellipse(Vector3D.ZERO,
  292.                                              new Vector3D(p.getX() / r, p.getY() / r, 0),
  293.                                              Vector3D.PLUS_K,
  294.                                              getA(), getC(), bodyFrame);

  295.         // find the closest point in the meridian plane
  296.         final Vector3D groundPoint = meridian.toSpace(meridian.projectToEllipse(new Vector2D(r, z)));

  297.         // transform point back to initial frame
  298.         return toBody.getInverse().transformPosition(groundPoint);

  299.     }

  300.     /** {@inheritDoc} */
  301.     public TimeStampedPVCoordinates projectToGround(final TimeStampedPVCoordinates pv, final Frame frame) {

  302.         // transform point to body frame
  303.         final Transform                toBody        = frame.getTransformTo(bodyFrame, pv.getDate());
  304.         final TimeStampedPVCoordinates pvInBodyFrame = toBody.transformPVCoordinates(pv);
  305.         final Vector3D                 p             = pvInBodyFrame.getPosition();
  306.         final double                   r             = FastMath.hypot(p.getX(), p.getY());

  307.         // set up the 2D ellipse corresponding to first principal curvature along meridian
  308.         final Vector3D meridian = new Vector3D(p.getX() / r, p.getY() / r, 0);
  309.         final Ellipse firstPrincipalCurvature =
  310.                 new Ellipse(Vector3D.ZERO, meridian, Vector3D.PLUS_K, getA(), getC(), bodyFrame);

  311.         // project coordinates in the meridian plane
  312.         final TimeStampedPVCoordinates gpFirst = firstPrincipalCurvature.projectToEllipse(pvInBodyFrame);
  313.         final Vector3D                 gpP     = gpFirst.getPosition();
  314.         final double                   gr      = MathArrays.linearCombination(gpP.getX(), meridian.getX(),
  315.                                                                               gpP.getY(), meridian.getY());
  316.         final double                   gz      = gpP.getZ();

  317.         // topocentric frame
  318.         final Vector3D east   = new Vector3D(-meridian.getY(), meridian.getX(), 0);
  319.         final Vector3D zenith = new Vector3D(gr * getC() / getA(), meridian, gz * getA() / getC(), Vector3D.PLUS_K).normalize();
  320.         final Vector3D north  = Vector3D.crossProduct(zenith, east);

  321.         // set up the ellipse corresponding to second principal curvature in the zenith/east plane
  322.         final Ellipse secondPrincipalCurvature  = getPlaneSection(gpP, north);
  323.         final TimeStampedPVCoordinates gpSecond = secondPrincipalCurvature.projectToEllipse(pvInBodyFrame);

  324.         final Vector3D gpV = gpFirst.getVelocity().add(gpSecond.getVelocity());
  325.         final Vector3D gpA = gpFirst.getAcceleration().add(gpSecond.getAcceleration());

  326.         // moving projected point
  327.         final TimeStampedPVCoordinates groundPV =
  328.                 new TimeStampedPVCoordinates(pv.getDate(), gpP, gpV, gpA);

  329.         // transform moving projected point back to initial frame
  330.         return toBody.getInverse().transformPVCoordinates(groundPV);

  331.     }

  332.     /** {@inheritDoc}
  333.      * <p>
  334.      * This method is based on Toshio Fukushima's algorithm which uses Halley's method.
  335.      * <a href="https://www.researchgate.net/publication/227215135_Transformation_from_Cartesian_to_Geodetic_Coordinates_Accelerated_by_Halley's_Method">
  336.      * transformation from Cartesian to Geodetic Coordinates Accelerated by Halley's Method</a>,
  337.      * Toshio Fukushima, Journal of Geodesy 9(12):689-693, February 2006
  338.      * </p>
  339.      * <p>
  340.      * Some changes have been added to the original method:
  341.      * <ul>
  342.      *   <li>in order to handle more accurately corner cases near the pole</li>
  343.      *   <li>in order to handle properly corner cases near the equatorial plane, even far inside the ellipsoid</li>
  344.      *   <li>in order to handle very flat ellipsoids</li>
  345.      * </ul>
  346.      * </p>
  347.      */
  348.     public GeodeticPoint transform(final Vector3D point, final Frame frame, final AbsoluteDate date) {

  349.         // transform point to body frame
  350.         final Vector3D pointInBodyFrame = frame.getTransformTo(bodyFrame, date).transformPosition(point);
  351.         final double   r2               = pointInBodyFrame.getX() * pointInBodyFrame.getX() +
  352.                                           pointInBodyFrame.getY() * pointInBodyFrame.getY();
  353.         final double   r                = FastMath.sqrt(r2);
  354.         final double   z                = pointInBodyFrame.getZ();

  355.         final double   lambda           = FastMath.atan2(pointInBodyFrame.getY(), pointInBodyFrame.getX());

  356.         double h;
  357.         double phi;
  358.         if (r <= ANGULAR_THRESHOLD * FastMath.abs(z)) {
  359.             // the point is almost on the polar axis, approximate the ellipsoid with
  360.             // the osculating sphere whose center is at evolute cusp along polar axis
  361.             final double osculatingRadius = ae2 / getC();
  362.             final double evoluteCuspZ     = FastMath.copySign(getA() * e2 / g, -z);
  363.             final double deltaZ           = z - evoluteCuspZ;
  364.             // we use π/2 - atan(r/Δz) instead of atan(Δz/r) for accuracy purposes, as r is much smaller than Δz
  365.             phi = FastMath.copySign(0.5 * FastMath.PI - FastMath.atan(r / FastMath.abs(deltaZ)), deltaZ);
  366.             h   = FastMath.hypot(deltaZ, r) - osculatingRadius;
  367.         } else if (FastMath.abs(z) <= ANGULAR_THRESHOLD * r) {
  368.             // the point is almost on the major axis

  369.             final double osculatingRadius = ap2 / getA();
  370.             final double evoluteCuspR     = getA() * e2;
  371.             final double deltaR           = r - evoluteCuspR;
  372.             if (deltaR >= 0) {
  373.                 // the point is outside of the ellipse evolute, approximate the ellipse
  374.                 // with the osculating circle whose center is at evolute cusp along major axis
  375.                 phi = (deltaR == 0) ? 0.0 : FastMath.atan(z / deltaR);
  376.                 h   = FastMath.hypot(deltaR, z) - osculatingRadius;
  377.             } else {
  378.                 // the point is on the part of the major axis within ellipse evolute
  379.                 // we can compute the closest ellipse point analytically, and it is NOT near the equator
  380.                 final double rClose = r / e2;
  381.                 final double zClose = FastMath.copySign(g * FastMath.sqrt(ae2 - rClose * rClose), z);
  382.                 phi = FastMath.atan((zClose - z) / (rClose - r));
  383.                 h   = -FastMath.hypot(r - rClose, z - zClose);
  384.             }

  385.         } else {
  386.             // use Toshio Fukushima method, with several iterations
  387.             final double epsPhi = 1.0e-15;
  388.             final double epsH   = 1.0e-14 * FastMath.max(getA(), FastMath.sqrt(r2 + z * z));
  389.             final double c     = getA() * e2;
  390.             final double absZ  = FastMath.abs(z);
  391.             final double zc    = g * absZ;
  392.             double sn  = absZ;
  393.             double sn2 = sn * sn;
  394.             double cn  = g * r;
  395.             double cn2 = cn * cn;
  396.             double an2 = cn2 + sn2;
  397.             double an  = FastMath.sqrt(an2);
  398.             double bn  = 0;
  399.             phi = Double.POSITIVE_INFINITY;
  400.             h   = Double.POSITIVE_INFINITY;
  401.             for (int i = 0; i < 10; ++i) { // this usually converges in 2 iterations
  402.                 final double oldSn  = sn;
  403.                 final double oldCn  = cn;
  404.                 final double oldPhi = phi;
  405.                 final double oldH   = h;
  406.                 final double an3    = an2 * an;
  407.                 final double csncn  = c * sn * cn;
  408.                 bn    = 1.5 * csncn * ((r * sn - zc * cn) * an - csncn);
  409.                 sn    = (zc * an3 + c * sn2 * sn) * an3 - bn * sn;
  410.                 cn    = (r  * an3 - c * cn2 * cn) * an3 - bn * cn;
  411.                 if (sn * oldSn < 0 || cn < 0) {
  412.                     // the Halley iteration went too far, we restrict it and iterate again
  413.                     while (sn * oldSn < 0 || cn < 0) {
  414.                         sn = (sn + oldSn) / 2;
  415.                         cn = (cn + oldCn) / 2;
  416.                     }
  417.                 } else {

  418.                     // rescale components to avoid overflow when several iterations are used
  419.                     final int exp = (FastMath.getExponent(sn) + FastMath.getExponent(cn)) / 2;
  420.                     sn = FastMath.scalb(sn, -exp);
  421.                     cn = FastMath.scalb(cn, -exp);

  422.                     sn2 = sn * sn;
  423.                     cn2 = cn * cn;
  424.                     an2 = cn2 + sn2;
  425.                     an  = FastMath.sqrt(an2);

  426.                     final double cc = g * cn;
  427.                     h = (r * cc + absZ * sn - getA() * g * an) / FastMath.sqrt(an2 - e2 * cn2);
  428.                     if (FastMath.abs(oldH   - h)   < epsH) {
  429.                         phi = FastMath.copySign(FastMath.atan(sn / cc), z);
  430.                         if (FastMath.abs(oldPhi - phi) < epsPhi) {
  431.                             break;
  432.                         }
  433.                     }

  434.                 }

  435.             }
  436.         }

  437.         return new GeodeticPoint(phi, lambda, h);

  438.     }

  439.     /** {@inheritDoc}
  440.      * <p>
  441.      * This method is based on Toshio Fukushima's algorithm which uses Halley's method.
  442.      * <a href="https://www.researchgate.net/publication/227215135_Transformation_from_Cartesian_to_Geodetic_Coordinates_Accelerated_by_Halley's_Method">
  443.      * transformation from Cartesian to Geodetic Coordinates Accelerated by Halley's Method</a>,
  444.      * Toshio Fukushima, Journal of Geodesy 9(12):689-693, February 2006
  445.      * </p>
  446.      * <p>
  447.      * Some changes have been added to the original method:
  448.      * <ul>
  449.      *   <li>in order to handle more accurately corner cases near the pole</li>
  450.      *   <li>in order to handle properly corner cases near the equatorial plane, even far inside the ellipsoid</li>
  451.      *   <li>in order to handle very flat ellipsoids</li>
  452.      * </ul>
  453.      * </p>
  454.      */
  455.     public <T extends RealFieldElement<T>> FieldGeodeticPoint<T> transform(final FieldVector3D<T> point,
  456.                                                                            final Frame frame,
  457.                                                                            final FieldAbsoluteDate<T> date) {

  458.         // transform point to body frame
  459.         final FieldVector3D<T> pointInBodyFrame = frame.getTransformTo(bodyFrame, date).transformPosition(point);
  460.         final T   r2                            = pointInBodyFrame.getX().multiply(pointInBodyFrame.getX()).
  461.                                               add(pointInBodyFrame.getY().multiply(pointInBodyFrame.getY()));
  462.         final T   r                             = r2.sqrt();
  463.         final T   z                             = pointInBodyFrame.getZ();

  464.         final T   lambda                        = pointInBodyFrame.getY().atan2(pointInBodyFrame.getX());

  465.         T h;
  466.         T phi;
  467.         if (r.getReal() <= ANGULAR_THRESHOLD * FastMath.abs(z.getReal())) {
  468.             // the point is almost on the polar axis, approximate the ellipsoid with
  469.             // the osculating sphere whose center is at evolute cusp along polar axis
  470.             final double osculatingRadius = ae2 / getC();
  471.             final double evoluteCuspZ     = FastMath.copySign(getA() * e2 / g, -z.getReal());
  472.             final T      deltaZ           = z.subtract(evoluteCuspZ);
  473.             // we use π/2 - atan(r/Δz) instead of atan(Δz/r) for accuracy purposes, as r is much smaller than Δz
  474.             phi = r.divide(deltaZ.abs()).atan().negate().add(0.5 * FastMath.PI).copySign(deltaZ);
  475.             h   = deltaZ.hypot(r).subtract(osculatingRadius);
  476.         } else if (FastMath.abs(z.getReal()) <= ANGULAR_THRESHOLD * r.getReal()) {
  477.             // the point is almost on the major axis

  478.             final double osculatingRadius = ap2 / getA();
  479.             final double evoluteCuspR     = getA() * e2;
  480.             final T      deltaR           = r.subtract(evoluteCuspR);
  481.             if (deltaR.getReal() >= 0) {
  482.                 // the point is outside of the ellipse evolute, approximate the ellipse
  483.                 // with the osculating circle whose center is at evolute cusp along major axis
  484.                 phi = (deltaR.getReal() == 0) ? z.getField().getZero() : z.divide(deltaR).atan();
  485.                 h   = deltaR.hypot(z).subtract(osculatingRadius);
  486.             } else {
  487.                 // the point is on the part of the major axis within ellipse evolute
  488.                 // we can compute the closest ellipse point analytically, and it is NOT near the equator
  489.                 final T rClose = r.divide(e2);
  490.                 final T zClose = rClose.multiply(rClose).negate().add(ae2).sqrt().multiply(g).copySign(z);
  491.                 phi = zClose.subtract(z).divide(rClose.subtract(r)).atan();
  492.                 h   = r.subtract(rClose).hypot(z.subtract(zClose)).negate();
  493.             }

  494.         } else {
  495.             // use Toshio Fukushima method, with several iterations
  496.             final double epsPhi = 1.0e-15;
  497.             final double epsH   = 1.0e-14 * getA();
  498.             final double c      = getA() * e2;
  499.             final T      absZ   = z.abs();
  500.             final T      zc     = absZ.multiply(g);
  501.             T            sn     = absZ;
  502.             T            sn2    = sn.multiply(sn);
  503.             T            cn     = r.multiply(g);
  504.             T            cn2    = cn.multiply(cn);
  505.             T            an2    = cn2.add(sn2);
  506.             T            an     = an2.sqrt();
  507.             T            bn     = an.getField().getZero();
  508.             phi = an.getField().getZero().add(Double.POSITIVE_INFINITY);
  509.             h   = an.getField().getZero().add(Double.POSITIVE_INFINITY);
  510.             for (int i = 0; i < 10; ++i) { // this usually converges in 2 iterations
  511.                 final T oldSn  = sn;
  512.                 final T oldCn  = cn;
  513.                 final T oldPhi = phi;
  514.                 final T oldH   = h;
  515.                 final T an3    = an2.multiply(an);
  516.                 final T csncn  = sn.multiply(cn).multiply(c);
  517.                 bn    = csncn.multiply(1.5).multiply((r.multiply(sn).subtract(zc.multiply(cn))).multiply(an).subtract(csncn));
  518.                 sn    = zc.multiply(an3).add(sn2.multiply(sn).multiply(c)).multiply(an3).subtract(bn.multiply(sn));
  519.                 cn    = r.multiply(an3).subtract(cn2.multiply(cn).multiply(c)).multiply(an3).subtract(bn.multiply(cn));
  520.                 if (sn.getReal() * oldSn.getReal() < 0 || cn.getReal() < 0) {
  521.                     // the Halley iteration went too far, we restrict it and iterate again
  522.                     while (sn.getReal() * oldSn.getReal() < 0 || cn.getReal() < 0) {
  523.                         sn = sn.add(oldSn).multiply(0.5);
  524.                         cn = cn.add(oldCn).multiply(0.5);
  525.                     }
  526.                 } else {

  527.                     // rescale components to avoid overflow when several iterations are used
  528.                     final int exp = (FastMath.getExponent(sn.getReal()) + FastMath.getExponent(cn.getReal())) / 2;
  529.                     sn = sn.scalb(-exp);
  530.                     cn = cn.scalb(-exp);

  531.                     sn2 = sn.multiply(sn);
  532.                     cn2 = cn.multiply(cn);
  533.                     an2 = cn2.add(sn2);
  534.                     an  = an2.sqrt();

  535.                     final T cc = cn.multiply(g);
  536.                     h = r.multiply(cc).add(absZ.multiply(sn)).subtract(an.multiply(getA() * g)).divide(an2.subtract(cn2.multiply(e2)).sqrt());
  537.                     if (FastMath.abs(oldH.getReal()  - h.getReal())   < epsH) {
  538.                         phi = sn.divide(cc).atan().copySign(z);
  539.                         if (FastMath.abs(oldPhi.getReal() - phi.getReal()) < epsPhi) {
  540.                             break;
  541.                         }
  542.                     }

  543.                 }

  544.             }
  545.         }

  546.         return new FieldGeodeticPoint<>(phi, lambda, h);

  547.     }

  548.     /** Transform a Cartesian point to a surface-relative point.
  549.      * @param point Cartesian point
  550.      * @param frame frame in which Cartesian point is expressed
  551.      * @param date date of the computation (used for frames conversions)
  552.      * @return point at the same location but as a surface-relative point,
  553.      * using time as the single derivation parameter
  554.      */
  555.     public FieldGeodeticPoint<DerivativeStructure> transform(final PVCoordinates point,
  556.                                                              final Frame frame, final AbsoluteDate date) {

  557.         // transform point to body frame
  558.         final Transform toBody = frame.getTransformTo(bodyFrame, date);
  559.         final PVCoordinates pointInBodyFrame = toBody.transformPVCoordinates(point);
  560.         final FieldVector3D<DerivativeStructure> p = pointInBodyFrame.toDerivativeStructureVector(2);
  561.         final DerivativeStructure   pr2 = p.getX().multiply(p.getX()).add(p.getY().multiply(p.getY()));
  562.         final DerivativeStructure   pr  = pr2.sqrt();
  563.         final DerivativeStructure   pz  = p.getZ();

  564.         // project point on the ellipsoid surface
  565.         final TimeStampedPVCoordinates groundPoint = projectToGround(new TimeStampedPVCoordinates(date, pointInBodyFrame),
  566.                                                                      bodyFrame);
  567.         final FieldVector3D<DerivativeStructure> gp = groundPoint.toDerivativeStructureVector(2);
  568.         final DerivativeStructure   gpr2 = gp.getX().multiply(gp.getX()).add(gp.getY().multiply(gp.getY()));
  569.         final DerivativeStructure   gpr  = gpr2.sqrt();
  570.         final DerivativeStructure   gpz  = gp.getZ();

  571.         // relative position of test point with respect to its ellipse sub-point
  572.         final DerivativeStructure dr  = pr.subtract(gpr);
  573.         final DerivativeStructure dz  = pz.subtract(gpz);
  574.         final double insideIfNegative = g2 * (pr2.getReal() - ae2) + pz.getReal() * pz.getReal();

  575.         return new FieldGeodeticPoint<>(DerivativeStructure.atan2(gpz, gpr.multiply(g2)),
  576.                                                                   DerivativeStructure.atan2(p.getY(), p.getX()),
  577.                                                                   DerivativeStructure.hypot(dr, dz).copySign(insideIfNegative));
  578.     }

  579.     /** Replace the instance with a data transfer object for serialization.
  580.      * <p>
  581.      * This intermediate class serializes the files supported names, the
  582.      * ephemeris type and the body name.
  583.      * </p>
  584.      * @return data transfer object that will be serialized
  585.      */
  586.     private Object writeReplace() {
  587.         return new DataTransferObject(getA(), f, bodyFrame, angularThreshold);
  588.     }

  589.     /** Internal class used only for serialization. */
  590.     private static class DataTransferObject implements Serializable {

  591.         /** Serializable UID. */
  592.         private static final long serialVersionUID = 20130518L;

  593.         /** Equatorial radius. */
  594.         private final double ae;

  595.         /** Flattening. */
  596.         private final double f;

  597.         /** Body frame related to body shape. */
  598.         private final Frame bodyFrame;

  599.         /** Convergence limit. */
  600.         private final double angularThreshold;

  601.         /** Simple constructor.
  602.          * @param ae equatorial radius
  603.          * @param f the flattening (f = (a-b)/a)
  604.          * @param bodyFrame body frame related to body shape
  605.          * @param angularThreshold convergence limit
  606.          */
  607.         DataTransferObject(final double ae, final double f,
  608.                                   final Frame bodyFrame, final double angularThreshold) {
  609.             this.ae               = ae;
  610.             this.f                = f;
  611.             this.bodyFrame        = bodyFrame;
  612.             this.angularThreshold = angularThreshold;
  613.         }

  614.         /** Replace the deserialized data transfer object with a
  615.          * {@link JPLCelestialBody}.
  616.          * @return replacement {@link JPLCelestialBody}
  617.          */
  618.         private Object readResolve() {
  619.             final OneAxisEllipsoid ellipsoid = new OneAxisEllipsoid(ae, f, bodyFrame);
  620.             ellipsoid.setAngularThreshold(angularThreshold);
  621.             return ellipsoid;
  622.         }

  623.     }

  624. }