ExtendedEllipsoid.java

  1. /* Copyright 2013-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.rugged.utils;

  18. import org.hipparchus.geometry.euclidean.threed.Line;
  19. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  20. import org.hipparchus.util.FastMath;
  21. import org.hipparchus.util.MathArrays;
  22. import org.orekit.bodies.GeodeticPoint;
  23. import org.orekit.bodies.OneAxisEllipsoid;
  24. import org.orekit.frames.Frame;
  25. import org.orekit.rugged.errors.DumpManager;
  26. import org.orekit.rugged.errors.RuggedException;
  27. import org.orekit.rugged.errors.RuggedMessages;
  28. import org.orekit.time.AbsoluteDate;

  29. /** Transform provider from Spacecraft frame to observed body frame.
  30.  * @author Luc Maisonobe
  31.  */
  32. public class ExtendedEllipsoid extends OneAxisEllipsoid {

  33.     /** Serializable UID. */
  34.     private static final long serialVersionUID = 20140312L;

  35.     /** Convergence threshold for {@link #pointAtAltitude(Vector3D, Vector3D, double)}. */
  36.     private static final double ALTITUDE_CONVERGENCE = 1.0e-3;

  37.     /** Equatorial radius power 2. */
  38.     private final double a2;

  39.     /** Polar radius power 2. */
  40.     private final double b2;

  41.     /** Simple constructor.
  42.      * @param ae equatorial radius (m)
  43.      * @param f the flattening (f = (a-b)/a)
  44.      * @param bodyFrame body frame related to body shape
  45.      * @see org.orekit.frames.FramesFactory#getITRF(org.orekit.utils.IERSConventions, boolean)
  46.      */
  47.     public ExtendedEllipsoid(final double ae, final double f, final Frame bodyFrame) {
  48.         super(ae, f, bodyFrame);
  49.         a2 = ae * ae;
  50.         final double b = ae * (1.0 - f);
  51.         b2 = b * b;
  52.     }

  53.     /** {@inheritDoc} */
  54.     @Override
  55.     public Vector3D transform(final GeodeticPoint point) {
  56.         DumpManager.dumpEllipsoid(this);
  57.         return super.transform(point);
  58.     }

  59.     /** {@inheritDoc} */
  60.     @Override
  61.     public GeodeticPoint transform(final Vector3D point, final Frame frame, final AbsoluteDate date) {
  62.         DumpManager.dumpEllipsoid(this);
  63.         return super.transform(point, frame, date);
  64.     }

  65.     /** Get point at some latitude along a pixel line of sight.
  66.      * @param position cell position (in body frame) (m)
  67.      * @param los pixel line-of-sight, not necessarily normalized (in body frame)
  68.      * @param latitude latitude with respect to ellipsoid (rad)
  69.      * @param closeReference reference point used to select the closest solution
  70.      * when there are two points at the desired latitude along the line, it should
  71.      * be close to los surface intersection (m)
  72.      * @return point at latitude (m)
  73.      */
  74.     public Vector3D pointAtLatitude(final Vector3D position, final Vector3D los,
  75.                                     final double latitude, final Vector3D closeReference) {

  76.         DumpManager.dumpEllipsoid(this);

  77.         // find apex of iso-latitude cone, somewhere along polar axis
  78.         final double sinPhi  = FastMath.sin(latitude);
  79.         final double sinPhi2 = sinPhi * sinPhi;
  80.         final double e2      = getFlattening() * (2 - getFlattening());
  81.         final double apexZ   = -getA() * e2 * sinPhi / FastMath.sqrt(1 - e2 * sinPhi2);

  82.         // quadratic equation representing line intersection with iso-latitude cone
  83.         // a k² + 2 b k + c = 0
  84.         // when line of sight is almost along an iso-latitude generatrix, the quadratic
  85.         // equation above may become unsolvable due to numerical noise (we get catastrophic
  86.         // cancellation when computing b * b - a * c). So we set up the model in two steps,
  87.         // first searching k₀ such that position + k₀ los is close to closeReference, and
  88.         // then using position + k₀ los as the new initial position, which should be in
  89.         // the neighborhood of the solution
  90.         final double cosPhi  = FastMath.cos(latitude);
  91.         final double cosPhi2 = cosPhi * cosPhi;
  92.         final double k0 = Vector3D.dotProduct(closeReference.subtract(position), los) / los.getNormSq();

  93.         final Vector3D delta  = new Vector3D(MathArrays.linearCombination(1, position.getX(), k0, los.getX()),
  94.                                              MathArrays.linearCombination(1, position.getY(), k0, los.getY()),
  95.                                              MathArrays.linearCombination(1, position.getZ(), k0, los.getZ(), -1.0, apexZ));
  96.         final double   a     = MathArrays.linearCombination(+sinPhi2, los.getX() * los.getX() + los.getY() * los.getY(),
  97.                                                             -cosPhi2, los.getZ() * los.getZ());
  98.         final double   b      = MathArrays.linearCombination(+sinPhi2, MathArrays.linearCombination(delta.getX(), los.getX(),
  99.                                                                                                     delta.getY(), los.getY()),
  100.                                                              -cosPhi2, delta.getZ() * los.getZ());
  101.         final double   c      = MathArrays.linearCombination(+sinPhi2, delta.getX() * delta.getX() + delta.getY() * delta.getY(),
  102.                                                              -cosPhi2, delta.getZ() * delta.getZ());

  103.         // find the two intersections along the line
  104.         if (b * b < a * c) {
  105.             throw new RuggedException(RuggedMessages.LINE_OF_SIGHT_NEVER_CROSSES_LATITUDE,
  106.                                       FastMath.toDegrees(latitude));
  107.         }
  108.         final double s  = FastMath.sqrt(MathArrays.linearCombination(b, b, -a, c));
  109.         final double k1 = (b > 0) ? -(s + b) / a : c / (s - b);
  110.         final double k2 = c / (a * k1);

  111.         // the quadratic equation has two solutions
  112.         final boolean  k1IsOK = (delta.getZ() + k1 * los.getZ()) * latitude >= 0;
  113.         final boolean  k2IsOK = (delta.getZ() + k2 * los.getZ()) * latitude >= 0;
  114.         final double selectedK;
  115.         if (k1IsOK) {
  116.             if (k2IsOK) {
  117.                 // both solutions are in the good nappe,
  118.                 // select the one closest to the specified reference
  119.                 final double kRef = Vector3D.dotProduct(los, closeReference.subtract(position)) /
  120.                                     los.getNormSq() - k0;
  121.                 selectedK = FastMath.abs(k1 - kRef) <= FastMath.abs(k2 - kRef) ? k1 : k2;
  122.             } else {
  123.                 // only k1 is in the good nappe
  124.                 selectedK = k1;
  125.             }
  126.         } else {
  127.             if (k2IsOK) {
  128.                 // only k2 is in the good nappe
  129.                 selectedK = k2;
  130.             } else {
  131.                 // both solutions are in the wrong nappe,
  132.                 // there are no solutions
  133.                 throw new RuggedException(RuggedMessages.LINE_OF_SIGHT_NEVER_CROSSES_LATITUDE,
  134.                                           FastMath.toDegrees(latitude));
  135.             }
  136.         }

  137.         // compute point
  138.         return new Vector3D(1, position, k0 + selectedK, los);

  139.     }

  140.     /** Get point at some longitude along a pixel line of sight.
  141.      * @param position cell position (in body frame) (m)
  142.      * @param los pixel line-of-sight, not necessarily normalized (in body frame)
  143.      * @param longitude longitude with respect to ellipsoid (rad)
  144.      * @return point at longitude (m)
  145.      */
  146.     public Vector3D pointAtLongitude(final Vector3D position, final Vector3D los, final double longitude) {

  147.         DumpManager.dumpEllipsoid(this);

  148.         // normal to meridian
  149.         final Vector3D normal = new Vector3D(-FastMath.sin(longitude), FastMath.cos(longitude), 0);
  150.         final double d = Vector3D.dotProduct(los, normal);
  151.         if (FastMath.abs(d) < 1.0e-12) {
  152.             throw new RuggedException(RuggedMessages.LINE_OF_SIGHT_NEVER_CROSSES_LONGITUDE,
  153.                                       FastMath.toDegrees(longitude));
  154.         }

  155.         // compute point
  156.         return new Vector3D(1, position, -Vector3D.dotProduct(position, normal) / d, los);

  157.     }

  158.     /** Get point on ground along a pixel line of sight.
  159.      * @param position cell position (in body frame) (m)
  160.      * @param los pixel line-of-sight, not necessarily normalized (in body frame)
  161.      * @param centralLongitude reference longitude lc such that the point longitude will
  162.      * be normalized between lc-π and lc+π (rad)
  163.      * @return point on ground
  164.      */
  165.     public NormalizedGeodeticPoint pointOnGround(final Vector3D position, final Vector3D los,
  166.                                                  final double centralLongitude) {

  167.         DumpManager.dumpEllipsoid(this);
  168.         final GeodeticPoint gp =
  169.                 getIntersectionPoint(new Line(position, new Vector3D(1, position, 1e6, los), 1.0e-12),
  170.                         position, getBodyFrame(), null);
  171.         if (gp == null) {
  172.             throw new RuggedException(RuggedMessages.LINE_OF_SIGHT_DOES_NOT_REACH_GROUND);
  173.         }
  174.         return new NormalizedGeodeticPoint(gp.getLatitude(), gp.getLongitude(), gp.getAltitude(),
  175.                 centralLongitude);
  176.     }

  177.     /** Get point at some altitude along a pixel line of sight.
  178.      * @param position cell position (in body frame) (m)
  179.      * @param los pixel line-of-sight, not necessarily normalized (in body frame)
  180.      * @param altitude altitude with respect to ellipsoid (m)
  181.      * @return point at altitude (m)
  182.      */
  183.     public Vector3D pointAtAltitude(final Vector3D position, final Vector3D los, final double altitude) {

  184.         DumpManager.dumpEllipsoid(this);

  185.         // point on line closest to origin
  186.         final double   los2   = los.getNormSq();
  187.         final double   dot    = Vector3D.dotProduct(position, los);
  188.         final double   k0     = -dot / los2;
  189.         final Vector3D close0 = new Vector3D(1, position, k0, los);

  190.         // very rough guess: if body is spherical, the desired point on line
  191.         // is at distance ae + altitude from origin
  192.         final double r        = getEquatorialRadius() + altitude;
  193.         final double delta2   = r * r - close0.getNormSq();
  194.         if (delta2 < 0) {
  195.             throw new RuggedException(RuggedMessages.LINE_OF_SIGHT_NEVER_CROSSES_ALTITUDE, altitude);
  196.         }
  197.         final double deltaK   = FastMath.sqrt(delta2 / los2);
  198.         final double k1       = k0 + deltaK;
  199.         final double k2       = k0 - deltaK;
  200.         double k              = (FastMath.abs(k1) <= FastMath.abs(k2)) ? k1 : k2;

  201.         // this loop generally converges in 3 iterations
  202.         for (int i = 0; i < 100; ++i) {

  203.             final Vector3D      point   = new Vector3D(1, position, k, los);
  204.             final GeodeticPoint gpK     = transform(point, getBodyFrame(), null);
  205.             final double        deltaH  = altitude - gpK.getAltitude();
  206.             if (FastMath.abs(deltaH) <= ALTITUDE_CONVERGENCE) {
  207.                 return point;
  208.             }

  209.             // improve the offset using linear ratio between
  210.             // altitude variation and displacement along line-of-sight
  211.             k += deltaH / Vector3D.dotProduct(gpK.getZenith(), los);

  212.         }

  213.         // this should never happen
  214.         throw new RuggedException(RuggedMessages.LINE_OF_SIGHT_NEVER_CROSSES_ALTITUDE, altitude);
  215.     }

  216.     /** Convert a line-of-sight from Cartesian to topocentric.
  217.      * @param point geodetic point on the line-of-sight
  218.      * @param los line-of-sight, not necessarily normalized (in body frame and Cartesian coordinates)
  219.      * @return line-of-sight in topocentric frame (East, North, Zenith) of the point,
  220.      * scaled to match radians in the horizontal plane and meters along the vertical axis
  221.      */
  222.     public Vector3D convertLos(final GeodeticPoint point, final Vector3D los) {

  223.         // Cartesian coordinates of the topocentric frame origin
  224.         final Vector3D p3D = transform(point);

  225.         // local radius of curvature in the East-West direction (parallel)
  226.         final double r     = FastMath.hypot(p3D.getX(), p3D.getY());

  227.         // local radius of curvature in the North-South direction (meridian)
  228.         final double b2r   = b2 * r;
  229.         final double b4r2  = b2r * b2r;
  230.         final double a2z   = a2 * p3D.getZ();
  231.         final double a4z2  = a2z * a2z;
  232.         final double q     = a4z2 + b4r2;
  233.         final double rho   = q * FastMath.sqrt(q) / (b2 * a4z2 + a2 * b4r2);

  234.         final double norm = los.getNorm();
  235.         return new Vector3D(Vector3D.dotProduct(los, point.getEast())   / (norm * r),
  236.                             Vector3D.dotProduct(los, point.getNorth())  / (norm * rho),
  237.                             Vector3D.dotProduct(los, point.getZenith()) / norm);

  238.     }

  239.     /** Convert a line-of-sight from Cartesian to topocentric.
  240.      * @param primary reference point on the line-of-sight (in body frame and Cartesian coordinates)
  241.      * @param secondary secondary point on the line-of-sight, only used to define a direction
  242.      * with respect to the primary point (in body frame and Cartesian coordinates)
  243.      * @return line-of-sight in topocentric frame (East, North, Zenith) of the point,
  244.      * scaled to match radians in the horizontal plane and meters along the vertical axis
  245.      */
  246.     public Vector3D convertLos(final Vector3D primary, final Vector3D secondary) {

  247.         // switch to geodetic coordinates using primary point as reference
  248.         final GeodeticPoint point = transform(primary, getBodyFrame(), null);
  249.         final Vector3D      los   = secondary.subtract(primary);

  250.         // convert line of sight
  251.         return convertLos(point, los);
  252.     }

  253.     /** Transform a cartesian point to a surface-relative point.
  254.      * @param point cartesian point (m)
  255.      * @param frame frame in which cartesian point is expressed
  256.      * @param date date of the computation (used for frames conversions)
  257.      * @param centralLongitude reference longitude lc such that the point longitude will
  258.      * be normalized between lc-π and lc+π (rad)
  259.      * @return point at the same location but as a surface-relative point
  260.      */
  261.     public NormalizedGeodeticPoint transform(final Vector3D point, final Frame frame, final AbsoluteDate date,
  262.                                              final double centralLongitude) {
  263.         final GeodeticPoint gp = transform(point, frame, date);
  264.         return new NormalizedGeodeticPoint(gp.getLatitude(), gp.getLongitude(), gp.getAltitude(),
  265.                                            centralLongitude);
  266.     }

  267. }