OneAxisEllipsoid.java

  1. /* Copyright 2002-2013 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.apache.commons.math3.analysis.UnivariateFunction;
  20. import org.apache.commons.math3.analysis.solvers.BracketedUnivariateSolver;
  21. import org.apache.commons.math3.analysis.solvers.BracketingNthOrderBrentSolver;
  22. import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
  23. import org.apache.commons.math3.geometry.euclidean.threed.Line;
  24. import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
  25. import org.apache.commons.math3.util.FastMath;
  26. import org.orekit.errors.OrekitException;
  27. import org.orekit.frames.Frame;
  28. import org.orekit.frames.Transform;
  29. import org.orekit.time.AbsoluteDate;


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

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

  35.  * @author Luc Maisonobe
  36.  */
  37. public class OneAxisEllipsoid implements BodyShape {

  38.     /** Serializable UID. */
  39.     private static final long serialVersionUID = 20130518L;

  40.     /** Body frame related to body shape. */
  41.     private final Frame bodyFrame;

  42.     /** Equatorial radius. */
  43.     private final double ae;

  44.     /** Equatorial radius power 2. */
  45.     private final double ae2;

  46.     /** Polar radius. */
  47.     private final double ap;

  48.     /** Polar radius power 2. */
  49.     private final double ap2;

  50.     /** Flattening. */
  51.     private final double f;

  52.     /** Eccentricity power 2. */
  53.     private final double e2;

  54.     /** 1 minus flatness. */
  55.     private final double g;

  56.     /** g * g. */
  57.     private final double g2;

  58.     /** Convergence limit. */
  59.     private double angularThreshold;

  60.     /** Simple constructor.
  61.      * <p>The following table provides conventional parameters for global Earth models:</p>
  62.      * <table border="1" cellpadding="5">
  63.      * <tr bgcolor="#ccccff"><th>model</th><th>a<sub>e</sub> (m)</th><th>f</th></tr>
  64.      * <tr><td bgcolor="#eeeeff">GRS 80</td><td>6378137.0</td><td>1.0 / 298.257222101</td></tr>
  65.      * <tr><td bgcolor="#eeeeff">WGS84</td><td>6378137.0</td><td>1.0 / 298.257223563</td></tr>
  66.      * </table>
  67.      * @param ae equatorial radius
  68.      * @param f the flattening (f = (a-b)/a)
  69.      * @param bodyFrame body frame related to body shape
  70.      * @see org.orekit.frames.FramesFactory#getITRF(org.orekit.utils.IERSConventions, boolean)
  71.      */
  72.     public OneAxisEllipsoid(final double ae, final double f, final Frame bodyFrame) {
  73.         this.f   = f;
  74.         this.ae  = ae;
  75.         this.ae2 = ae * ae;
  76.         this.e2  = f * (2.0 - f);
  77.         this.g   = 1.0 - f;
  78.         this.g2  = g * g;
  79.         this.ap  = ae * g;
  80.         this.ap2 = ap * ap;
  81.         setAngularThreshold(1.0e-12);
  82.         this.bodyFrame = bodyFrame;
  83.     }

  84.     /** Set the close approach threshold.
  85.      * @param closeApproachThreshold close approach threshold (no unit)
  86.      * @deprecated as of 6.1, this threshold is not used anymore
  87.      */
  88.     @Deprecated
  89.     public void setCloseApproachThreshold(final double closeApproachThreshold) {
  90.         // unused
  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 ae;
  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.     /** Get the body frame related to body shape.
  117.      * @return body frame related to body shape
  118.      */
  119.     public Frame getBodyFrame() {
  120.         return bodyFrame;
  121.     }

  122.     /** {@inheritDoc} */
  123.     public GeodeticPoint getIntersectionPoint(final Line line, final Vector3D close,
  124.                                               final Frame frame, final AbsoluteDate date)
  125.         throws OrekitException {

  126.         // transform line and close to body frame
  127.         final Transform frameToBodyFrame = frame.getTransformTo(bodyFrame, date);
  128.         final Line lineInBodyFrame = frameToBodyFrame.transformLine(line);
  129.         final Vector3D closeInBodyFrame = frameToBodyFrame.transformPosition(close);
  130.         final double closeAbscissa = lineInBodyFrame.toSubSpace(closeInBodyFrame).getX();

  131.         // compute some miscellaneous variables outside of the loop
  132.         final Vector3D point    = lineInBodyFrame.getOrigin();
  133.         final double x          = point.getX();
  134.         final double y          = point.getY();
  135.         final double z          = point.getZ();
  136.         final double z2         = z * z;
  137.         final double r2         = x * x + y * y;

  138.         final Vector3D direction = lineInBodyFrame.getDirection();
  139.         final double dx         = direction.getX();
  140.         final double dy         = direction.getY();
  141.         final double dz         = direction.getZ();
  142.         final double cz2        = dx * dx + dy * dy;

  143.         // abscissa of the intersection as a root of a 2nd degree polynomial :
  144.         // a k^2 - 2 b k + c = 0
  145.         final double a  = 1.0 - e2 * cz2;
  146.         final double b  = -(g2 * (x * dx + y * dy) + z * dz);
  147.         final double c  = g2 * (r2 - ae2) + z2;
  148.         final double b2 = b * b;
  149.         final double ac = a * c;
  150.         if (b2 < ac) {
  151.             return null;
  152.         }
  153.         final double s  = FastMath.sqrt(b2 - ac);
  154.         final double k1 = (b < 0) ? (b - s) / a : c / (b + s);
  155.         final double k2 = c / (a * k1);

  156.         // select the right point
  157.         final double k =
  158.             (FastMath.abs(k1 - closeAbscissa) < FastMath.abs(k2 - closeAbscissa)) ? k1 : k2;
  159.         final Vector3D intersection = lineInBodyFrame.toSpace(new Vector1D(k));
  160.         final double ix = intersection.getX();
  161.         final double iy = intersection.getY();
  162.         final double iz = intersection.getZ();

  163.         final double lambda = FastMath.atan2(iy, ix);
  164.         final double phi    = FastMath.atan2(iz, g2 * FastMath.sqrt(ix * ix + iy * iy));
  165.         return new GeodeticPoint(phi, lambda, 0.0);

  166.     }

  167.     /** Transform a surface-relative point to a cartesian point.
  168.      * @param point surface-relative point
  169.      * @return point at the same location but as a cartesian point
  170.      */
  171.     public Vector3D transform(final GeodeticPoint point) {
  172.         final double longitude = point.getLongitude();
  173.         final double cLambda   = FastMath.cos(longitude);
  174.         final double sLambda   = FastMath.sin(longitude);
  175.         final double latitude  = point.getLatitude();
  176.         final double cPhi      = FastMath.cos(latitude);
  177.         final double sPhi      = FastMath.sin(latitude);
  178.         final double h         = point.getAltitude();
  179.         final double n         = ae / FastMath.sqrt(1.0 - e2 * sPhi * sPhi);
  180.         final double r         = (n + h) * cPhi;
  181.         return new Vector3D(r * cLambda, r * sLambda, (g2 * n + h) * sPhi);
  182.     }

  183.     /** Transform a cartesian point to a surface-relative point.
  184.      * @param point cartesian point
  185.      * @param frame frame in which cartesian point is expressed
  186.      * @param date date of the point in given frame
  187.      * @return point at the same location but as a surface-relative point,
  188.      * expressed in body frame
  189.      * @exception OrekitException if point cannot be converted to body frame
  190.      */
  191.     public GeodeticPoint transform(final Vector3D point, final Frame frame,
  192.                                    final AbsoluteDate date)
  193.         throws OrekitException {

  194.         // transform line to body frame
  195.         final Vector3D pointInBodyFrame =
  196.             frame.getTransformTo(bodyFrame, date).transformPosition(point);
  197.         final double lambda = FastMath.atan2(pointInBodyFrame.getY(), pointInBodyFrame.getX());

  198.         // compute some miscellaneous variables outside of the loop
  199.         final double z  = pointInBodyFrame.getZ();
  200.         final double z2 = z * z;
  201.         final double r2 = pointInBodyFrame.getX() * pointInBodyFrame.getX() +
  202.                           pointInBodyFrame.getY() * pointInBodyFrame.getY();
  203.         final double r  = FastMath.sqrt(r2);

  204.         if (r <= angularThreshold * FastMath.abs(z)) {
  205.             // the point is almost on the minor axis, approximate the ellipse with
  206.             // the osculating circle whose center is at evolute cusp along minor axis
  207.             final double osculatingRadius = ae2 / ap;
  208.             final double evoluteCusp      = ae * e2 / g;
  209.             final double delta            = z + FastMath.copySign(evoluteCusp, z);
  210.             return new GeodeticPoint(FastMath.atan2(delta, r), lambda,
  211.                                      FastMath.hypot(delta, r) - osculatingRadius);
  212.         }

  213.         // find ellipse point closest to test point
  214.         final double[] ellipsePoint;
  215.         if (FastMath.abs(z) <= angularThreshold * r) {
  216.             // the point is almost on the major axis

  217.             final double osculatingRadius = ap2 / ae;
  218.             final double evoluteCusp      = ae * e2;
  219.             final double delta            = r - evoluteCusp;
  220.             if (delta >= 0) {
  221.                 // the point is outside of the ellipse evolute, approximate the ellipse
  222.                 // with the osculating circle whose center is at evolute cusp along major axis
  223.                 return new GeodeticPoint(FastMath.atan2(z, delta), lambda,
  224.                                          FastMath.hypot(z, delta) - osculatingRadius);
  225.             }

  226.             // the point is on the part of the major axis within ellipse evolute
  227.             // we can compute the closest ellipse point analytically
  228.             final double rEllipse = r / e2;
  229.             ellipsePoint = new double[] {
  230.                 rEllipse,
  231.                 FastMath.copySign(g * FastMath.sqrt(ae2 - rEllipse * rEllipse), z)
  232.             };

  233.         } else {

  234.             final ClosestPointFinder finder = new ClosestPointFinder(r, z);
  235.             final double rho;
  236.             if (e2 >= angularThreshold) {
  237.                 // search the nadir point on the major axis,
  238.                 // somewhere within the evolute, i.e. between 0 and ae * e2
  239.                 // we use a slight margin factor 1.1 to make sure we properly bracket
  240.                 // the solution even for points very close to major axis
  241.                 final BracketedUnivariateSolver<UnivariateFunction> solver =
  242.                         new BracketingNthOrderBrentSolver(angularThreshold * ap, 5);
  243.                 rho = solver.solve(100, finder, 0, 1.1 * ae * e2);
  244.             } else {
  245.                 // the evolute is almost reduced to the central point,
  246.                 // the ellipsoid is almost a sphere
  247.                 rho = 0;
  248.             }
  249.             ellipsePoint = finder.intersectionPoint(rho);

  250.         }

  251.         // relative position of test point with respect to its ellipse sub-point
  252.         final double dr = r - ellipsePoint[0];
  253.         final double dz = z - ellipsePoint[1];
  254.         final double insideIfNegative = g2 * (r2 - ae2) + z2;

  255.         return new GeodeticPoint(FastMath.atan2(ellipsePoint[1], g2 * ellipsePoint[0]),
  256.                                  lambda,
  257.                                  FastMath.copySign(FastMath.hypot(dr, dz), insideIfNegative));

  258.     }

  259.     /** Local class for finding closest point to ellipse.
  260.      * <p>
  261.      * We consider a guessed equatorial point E somewhere along
  262.      * the ellipse major axis, and within the ellipse evolute curve.
  263.      * This point is defined by its coordinates (&rho;, 0).
  264.      * </p>
  265.      * <p>
  266.      * A point P belonging to line (E, A) can be computed from a
  267.      * parameter k as follows:
  268.      * </p>
  269.      * <pre>
  270.      *   u = &rho; + k * (r - &rho;)
  271.      *   v = 0 + k * (z - 0)
  272.      * </pre>
  273.      * <p>
  274.      * For some specific positive value of k, the line (E, A)
  275.      * intersects the ellipse at a point I which lies in the same quadrant
  276.      * as test point A. There is another intersection point with k
  277.      * negative, but this intersection point is not in the same quadrant
  278.      * as test point A.
  279.      * </p>
  280.      * <p>
  281.      * The line joining point I and the center of the corresponding
  282.      * osculating circle (i.e. the normal to the ellipse at point I)
  283.      * crosses major axis at another equatorial point E'. If E and E' are
  284.      * the same points, then the guessed point E is the true nadir. When
  285.      * the point I is close to the major axis, the intersection of the
  286.      * line I with equatorial line is not well defined, but the limit
  287.      * position of point E' can be computed, it is the cusp of the
  288.      * ellipse evolute.
  289.      * </p>
  290.      * <p>
  291.      * This class provides methods to compute I and to compute the
  292.      * offset between E' and E, which allows to find the value
  293.      * of &rho; such that I is the closest point of the ellipse to A.
  294.      * </p>
  295.      */
  296.     private class ClosestPointFinder implements UnivariateFunction {

  297.         /** Abscissa of test point A along ellipse major axis. */
  298.         private final double r;

  299.         /** Ordinate of test point A along ellipse minor axis. */
  300.         private final double z;

  301.         /** Simple constructor.
  302.          * @param r abscissa of test point A along ellipse major axis
  303.          * @param z ordinate of test point A along ellipse minor axis
  304.          */
  305.         public ClosestPointFinder(final double r, final double z) {
  306.             this.r = r;
  307.             this.z = z;
  308.         }

  309.         /** Compute intersection point I.
  310.          * @param rho guessed equatorial point radius
  311.          * @return coordinates of intersection point I
  312.          */
  313.         private double[] intersectionPoint(final double rho) {
  314.             final double k = kOnEllipse(rho);
  315.             return new double[] {
  316.                 rho + k * (r - rho),
  317.                 k * z
  318.             };
  319.         }

  320.         /** Compute parameter k of intersection point I.
  321.          * @param rho guessed equatorial point radius
  322.          * @return value of parameter k such that line point belongs to the ellipse
  323.          */
  324.         private double kOnEllipse(final double rho) {

  325.             // rho defines a point on the ellipse major axis E with coordinates (rho, 0)
  326.             // the fixed test point A has coordinates (r, z)
  327.             // the coordinates (u, v) of point P belonging to line (E, A) can be
  328.             // computed from a parameter k as follows:
  329.             //     u = rho + k * (r - rho)
  330.             //     v = 0   + k * (z - 0)
  331.             // if P also belongs to the ellipse, the following quadratic
  332.             // equation in k holds: a * k^2 + 2 * b * k + c = 0
  333.             final double dr = r - rho;
  334.             final double a  = ap2 * dr * dr + ae2 * z * z;
  335.             final double b  = ap2 * rho * dr;
  336.             final double c  = ap2 * (rho - ae) * (rho + ae);

  337.             // positive root of the quadratic equation
  338.             final double s = FastMath.sqrt(b * b - a * c);
  339.             return (b > 0) ? -c / (s + b) : (s - b) / a;

  340.         }

  341.         /** Compute offset between guessed equatorial point and nadir.
  342.          * <p>
  343.          * We consider a guessed equatorial point E somewhere along
  344.          * the ellipse major axis, and within the ellipse evolute curve.
  345.          * The line (E, A) intersects the ellipse at some point P. The
  346.          * line segment starting at point P and going along the interior
  347.          * normal of the ellipse crosses major axis at another equatorial
  348.          * point E'. If E and E' are the same points, then the guessed
  349.          * point E is the true nadir. This method compute the offset
  350.          * between E and E' along major axis.
  351.          * </p>
  352.          * @param rho guessed equatorial point radius
  353.          * (point E is at coordinates (rho, 0) in the ellipse canonical axes system)
  354.          * @return offset between E and E'
  355.          */
  356.         @Override
  357.         public double value(final double rho) {

  358.             // intersection of line (E, A) with ellipse
  359.             final double k = kOnEllipse(rho);
  360.             final double u = rho + k * (r - rho);

  361.             // equatorial point E' in the nadir direction of P
  362.             final double rhoPrime = u * e2;

  363.             // offset between guessed point and recovered nadir point
  364.             return rhoPrime - rho;

  365.         }

  366.     }

  367.     /** Replace the instance with a data transfer object for serialization.
  368.      * <p>
  369.      * This intermediate class serializes the files supported names, the ephemeris type
  370.      * and the body name.
  371.      * </p>
  372.      * @return data transfer object that will be serialized
  373.      */
  374.     private Object writeReplace() {
  375.         return new DataTransferObject(ae, f, bodyFrame, angularThreshold);
  376.     }

  377.     /** Internal class used only for serialization. */
  378.     private static class DataTransferObject implements Serializable {

  379.         /** Serializable UID. */
  380.         private static final long serialVersionUID = 20130518L;

  381.         /** Equatorial radius. */
  382.         private final double ae;

  383.         /** Flattening. */
  384.         private final double f;

  385.         /** Body frame related to body shape. */
  386.         private final Frame bodyFrame;

  387.         /** Convergence limit. */
  388.         private final double angularThreshold;

  389.         /** Simple constructor.
  390.          * @param ae equatorial radius
  391.          * @param f the flattening (f = (a-b)/a)
  392.          * @param bodyFrame body frame related to body shape
  393.          * @param angularThreshold convergence limit
  394.          */
  395.         public DataTransferObject(final double ae, final double f, final Frame bodyFrame,
  396.                                   final double angularThreshold) {
  397.             this.ae               = ae;
  398.             this.f                = f;
  399.             this.bodyFrame        = bodyFrame;
  400.             this.angularThreshold = angularThreshold;
  401.         }

  402.         /** Replace the deserialized data transfer object with a {@link JPLCelestialBody}.
  403.          * @return replacement {@link JPLCelestialBody}
  404.          */
  405.         private Object readResolve() {
  406.             final OneAxisEllipsoid ellipsoid = new OneAxisEllipsoid(ae, f, bodyFrame);
  407.             ellipsoid.setAngularThreshold(angularThreshold);
  408.             return ellipsoid;
  409.         }

  410.     }

  411. }