TopocentricFrame.java

  1. /* Copyright 2002-2025 CS GROUP
  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.frames;

  18. import org.hipparchus.Field;
  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.analysis.UnivariateFunction;
  21. import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
  22. import org.hipparchus.analysis.solvers.UnivariateSolver;
  23. import org.hipparchus.exception.MathRuntimeException;
  24. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  25. import org.hipparchus.geometry.euclidean.threed.Rotation;
  26. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  27. import org.hipparchus.util.FastMath;
  28. import org.hipparchus.util.FieldSinCos;
  29. import org.hipparchus.util.MathUtils;
  30. import org.hipparchus.util.SinCos;
  31. import org.orekit.bodies.BodyShape;
  32. import org.orekit.bodies.FieldGeodeticPoint;
  33. import org.orekit.bodies.GeodeticPoint;
  34. import org.orekit.errors.OrekitException;
  35. import org.orekit.time.AbsoluteDate;
  36. import org.orekit.time.FieldAbsoluteDate;
  37. import org.orekit.utils.Constants;
  38. import org.orekit.utils.ExtendedPositionProvider;
  39. import org.orekit.utils.FieldPVCoordinates;
  40. import org.orekit.utils.FieldTrackingCoordinates;
  41. import org.orekit.utils.PVCoordinates;
  42. import org.orekit.utils.TimeStampedFieldPVCoordinates;
  43. import org.orekit.utils.TimeStampedPVCoordinates;
  44. import org.orekit.utils.TrackingCoordinates;


  45. /** Topocentric frame.
  46.  * <p>Frame associated to a position near the surface of a body shape.</p>
  47.  * <p>
  48.  * The origin of the frame is at the defining {@link GeodeticPoint geodetic point}
  49.  * location, and the right-handed canonical trihedra is:
  50.  * </p>
  51.  * <ul>
  52.  *   <li>X axis in the local horizontal plane (normal to zenith direction) and
  53.  *   following the local parallel towards East</li>
  54.  *   <li>Y axis in the horizontal plane (normal to zenith direction) and
  55.  *   following the local meridian towards North</li>
  56.  *   <li>Z axis towards Zenith direction</li>
  57.  * </ul>
  58.  * @author V&eacute;ronique Pommier-Maurussane
  59.  */
  60. public class TopocentricFrame extends Frame implements ExtendedPositionProvider {

  61.     /** Body shape on which the local point is defined. */
  62.     private final BodyShape parentShape;

  63.     /** Geodetic point where the topocentric frame is defined. */
  64.     private final GeodeticPoint point;

  65.     /** Cartesian point where the topocentric frame is defined.
  66.      * @since 12.0
  67.      */
  68.     private final Vector3D cartesianPoint;

  69.     /** Simple constructor.
  70.      * @param parentShape body shape on which the local point is defined
  71.      * @param point local surface point where topocentric frame is defined
  72.      * @param name the string representation
  73.      */
  74.     public TopocentricFrame(final BodyShape parentShape, final GeodeticPoint point,
  75.                             final String name) {

  76.         super(parentShape.getBodyFrame(),
  77.                 new Transform(AbsoluteDate.ARBITRARY_EPOCH,
  78.                         new Transform(AbsoluteDate.ARBITRARY_EPOCH,
  79.                                 parentShape.transform(point).negate()),
  80.                         new Transform(AbsoluteDate.ARBITRARY_EPOCH,
  81.                                 new Rotation(point.getEast(), point.getZenith(),
  82.                                         Vector3D.PLUS_I, Vector3D.PLUS_K),
  83.                                 Vector3D.ZERO)),
  84.                 name, false);
  85.         this.parentShape    = parentShape;
  86.         this.point          = point;
  87.         this.cartesianPoint = getTransformProvider().
  88.                 getStaticTransform(AbsoluteDate.ARBITRARY_EPOCH).
  89.                 getInverse().
  90.                 transformPosition(Vector3D.ZERO);
  91.     }

  92.     /** Get the body shape on which the local point is defined.
  93.      * @return body shape on which the local point is defined
  94.      */
  95.     public BodyShape getParentShape() {
  96.         return parentShape;
  97.     }

  98.     /** Get the surface point defining the origin of the frame.
  99.      * @return surface point defining the origin of the frame
  100.      */
  101.     public GeodeticPoint getPoint() {
  102.         return point;
  103.     }

  104.     /** Get the surface point defining the origin of the frame.
  105.      * @return surface point defining the origin of the frame in body frame
  106.      * @since 12.0
  107.      */
  108.     public Vector3D getCartesianPoint() {
  109.         return cartesianPoint;
  110.     }

  111.     /** Get the surface point defining the origin of the frame.
  112.      * @param <T> type of the elements
  113.      * @param field of the elements
  114.      * @return surface point defining the origin of the frame
  115.      * @since 9.3
  116.      */
  117.     public <T extends CalculusFieldElement<T>> FieldGeodeticPoint<T> getPoint(final Field<T> field) {
  118.         final T zero = field.getZero();
  119.         return new FieldGeodeticPoint<>(zero.newInstance(point.getLatitude()),
  120.                 zero.newInstance(point.getLongitude()),
  121.                 zero.newInstance(point.getAltitude()));
  122.     }

  123.     /** Get the zenith direction of topocentric frame, expressed in parent shape frame.
  124.      * <p>The zenith direction is defined as the normal to local horizontal plane.</p>
  125.      * @return unit vector in the zenith direction
  126.      * @see #getNadir()
  127.      */
  128.     public Vector3D getZenith() {
  129.         return point.getZenith();
  130.     }

  131.     /** Get the nadir direction of topocentric frame, expressed in parent shape frame.
  132.      * <p>The nadir direction is the opposite of zenith direction.</p>
  133.      * @return unit vector in the nadir direction
  134.      * @see #getZenith()
  135.      */
  136.     public Vector3D getNadir() {
  137.         return point.getNadir();
  138.     }

  139.     /** Get the north direction of topocentric frame, expressed in parent shape frame.
  140.      * <p>The north direction is defined in the horizontal plane
  141.      * (normal to zenith direction) and following the local meridian.</p>
  142.      * @return unit vector in the north direction
  143.      * @see #getSouth()
  144.      */
  145.     public Vector3D getNorth() {
  146.         return point.getNorth();
  147.     }

  148.     /** Get the south direction of topocentric frame, expressed in parent shape frame.
  149.      * <p>The south direction is the opposite of north direction.</p>
  150.      * @return unit vector in the south direction
  151.      * @see #getNorth()
  152.      */
  153.     public Vector3D getSouth() {
  154.         return point.getSouth();
  155.     }

  156.     /** Get the east direction of topocentric frame, expressed in parent shape frame.
  157.      * <p>The east direction is defined in the horizontal plane
  158.      * in order to complete direct triangle (east, north, zenith).</p>
  159.      * @return unit vector in the east direction
  160.      * @see #getWest()
  161.      */
  162.     public Vector3D getEast() {
  163.         return point.getEast();
  164.     }

  165.     /** Get the west direction of topocentric frame, expressed in parent shape frame.
  166.      * <p>The west direction is the opposite of east direction.</p>
  167.      * @return unit vector in the west direction
  168.      * @see #getEast()
  169.      */
  170.     public Vector3D getWest() {
  171.         return point.getWest();
  172.     }

  173.     /** Get the tracking coordinates of a point with regards to the local point.
  174.      * @param extPoint point for which elevation shall be computed
  175.      * @param frame frame in which the point is defined
  176.      * @param date computation date
  177.      * @return tracking coordinates of the point
  178.      * @since 12.0
  179.      */
  180.     public TrackingCoordinates getTrackingCoordinates(final Vector3D extPoint, final Frame frame,
  181.                                                       final AbsoluteDate date) {

  182.         // transform given point from given frame to topocentric frame
  183.         final Vector3D extPointTopo = transformPoint(extPoint, frame, date);

  184.         final double azimuth = computeAzimuthFromTopoPoint(extPointTopo);

  185.         return new TrackingCoordinates(azimuth, extPointTopo.getDelta(), extPointTopo.getNorm());

  186.     }

  187.     /** Get the tracking coordinates of a point with regards to the local point.
  188.      * @param <T> type of the field elements
  189.      * @param extPoint point for which elevation shall be computed
  190.      * @param frame frame in which the point is defined
  191.      * @param date computation date
  192.      * @return tracking coordinates of the point
  193.      * @since 12.0
  194.      */
  195.     public <T extends CalculusFieldElement<T>> FieldTrackingCoordinates<T> getTrackingCoordinates(final FieldVector3D<T> extPoint,
  196.                                                                                                   final Frame frame,
  197.                                                                                                   final FieldAbsoluteDate<T> date) {

  198.         // Transform given point from given frame to topocentric frame
  199.         final FieldVector3D<T> extPointTopo = transformPoint(extPoint, frame, date);

  200.         final T azimuth = computeAzimuthFromTopoPoint(extPointTopo);

  201.         return new FieldTrackingCoordinates<>(azimuth, extPointTopo.getDelta(), extPointTopo.getNorm());

  202.     }

  203.     /** Get the elevation of a point with regards to the local point.
  204.      * <p>The elevation is the angle between the local horizontal and
  205.      * the direction from local point to given point.</p>
  206.      * @param extPoint point for which elevation shall be computed
  207.      * @param frame frame in which the point is defined
  208.      * @param date computation date
  209.      * @return elevation of the point
  210.      */
  211.     public double getElevation(final Vector3D extPoint, final Frame frame,
  212.                                final AbsoluteDate date) {

  213.         // Transform given point from given frame to topocentric frame
  214.         final Vector3D extPointTopo = transformPoint(extPoint, frame, date);

  215.         return extPointTopo.getDelta();
  216.     }

  217.     /** Get the elevation of a point with regards to the local point.
  218.      * <p>The elevation is the angle between the local horizontal and
  219.      * the direction from local point to given point.</p>
  220.      * @param <T> type of the elements
  221.      * @param extPoint point for which elevation shall be computed
  222.      * @param frame frame in which the point is defined
  223.      * @param date computation date
  224.      * @return elevation of the point
  225.      * @since 9.3
  226.      */
  227.     public <T extends CalculusFieldElement<T>> T getElevation(final FieldVector3D<T> extPoint, final Frame frame,
  228.                                                               final FieldAbsoluteDate<T> date) {

  229.         // Transform given point from given frame to topocentric frame
  230.         final FieldVector3D<T> extPointTopo = transformPoint(extPoint, frame, date);

  231.         return extPointTopo.getDelta();
  232.     }

  233.     /** Get the azimuth of a point with regards to the topocentric frame center point.
  234.      * <p>The azimuth is the angle between the North direction at local point and
  235.      * the projection in local horizontal plane of the direction from local point
  236.      * to given point. Azimuth angles are counted clockwise, i.e positive towards the East.</p>
  237.      * @param extPoint point for which elevation shall be computed
  238.      * @param frame frame in which the point is defined
  239.      * @param date computation date
  240.      * @return azimuth of the point
  241.      */
  242.     public double getAzimuth(final Vector3D extPoint, final Frame frame,
  243.                              final AbsoluteDate date) {

  244.         // Transform given point from given frame to topocentric frame
  245.         final Vector3D extPointTopo = transformPoint(extPoint, frame, date);

  246.         return computeAzimuthFromTopoPoint(extPointTopo);

  247.     }

  248.     /** Get the azimuth of a point with regards to the topocentric frame center point.
  249.      * <p>The azimuth is the angle between the North direction at local point and
  250.      * the projection in local horizontal plane of the direction from local point
  251.      * to given point. Azimuth angles are counted clockwise, i.e positive towards the East.</p>
  252.      * @param <T> type of the elements
  253.      * @param extPoint point for which elevation shall be computed
  254.      * @param frame frame in which the point is defined
  255.      * @param date computation date
  256.      * @return azimuth of the point
  257.      * @since 9.3
  258.      */
  259.     public <T extends CalculusFieldElement<T>> T getAzimuth(final FieldVector3D<T> extPoint, final Frame frame,
  260.                                                             final FieldAbsoluteDate<T> date) {

  261.         // Transform given point from given frame to topocentric frame
  262.         final FieldVector3D<T> extPointTopo = transformPoint(extPoint, frame, date);

  263.         return computeAzimuthFromTopoPoint(extPointTopo);

  264.     }

  265.     /** Get the range of a point with regards to the topocentric frame center point.
  266.      * @param extPoint point for which range shall be computed
  267.      * @param frame frame in which the point is defined
  268.      * @param date computation date
  269.      * @return range (distance) of the point
  270.      */
  271.     public double getRange(final Vector3D extPoint, final Frame frame,
  272.                            final AbsoluteDate date) {

  273.         // Transform given point from given frame to topocentric frame
  274.         final Vector3D extPointTopo = transformPoint(extPoint, frame, date);

  275.         return extPointTopo.getNorm();

  276.     }

  277.     /** Get the range of a point with regards to the topocentric frame center point.
  278.      * @param <T> type of the elements
  279.      * @param extPoint point for which range shall be computed
  280.      * @param frame frame in which the point is defined
  281.      * @param date computation date
  282.      * @return range (distance) of the point
  283.      * @since 9.3
  284.      */
  285.     public <T extends CalculusFieldElement<T>> T getRange(final FieldVector3D<T> extPoint, final Frame frame,
  286.                                                           final FieldAbsoluteDate<T> date) {

  287.         // Transform given point from given frame to topocentric frame
  288.         final FieldVector3D<T> extPointTopo = transformPoint(extPoint, frame, date);

  289.         return extPointTopo.getNorm();

  290.     }

  291.     /** Get the range rate of a point with regards to the topocentric frame center point.
  292.      * @param extPV point/velocity for which range rate shall be computed
  293.      * @param frame frame in which the point is defined
  294.      * @param date computation date
  295.      * @return range rate of the point (positive if point departs from frame)
  296.      */
  297.     public double getRangeRate(final PVCoordinates extPV, final Frame frame,
  298.                                final AbsoluteDate date) {

  299.         // Transform given point from given frame to topocentric frame
  300.         final KinematicTransform t = frame.getKinematicTransformTo(this, date);
  301.         final PVCoordinates extPVTopo = t.transformOnlyPV(extPV);

  302.         // Compute range rate (doppler) : relative rate along the line of sight
  303.         return Vector3D.dotProduct(extPVTopo.getPosition(), extPVTopo.getVelocity()) /
  304.                 extPVTopo.getPosition().getNorm();

  305.     }

  306.     /** Get the range rate of a point with regards to the topocentric frame center point.
  307.      * @param <T> type of the elements
  308.      * @param extPV point/velocity for which range rate shall be computed
  309.      * @param frame frame in which the point is defined
  310.      * @param date computation date
  311.      * @return range rate of the point (positive if point departs from frame)
  312.      * @since 9.3
  313.      */
  314.     public <T extends CalculusFieldElement<T>> T getRangeRate(final FieldPVCoordinates<T> extPV, final Frame frame,
  315.                                                               final FieldAbsoluteDate<T> date) {

  316.         // Transform given point from given frame to topocentric frame
  317.         final FieldKinematicTransform<T> t = frame.getKinematicTransformTo(this, date);
  318.         final FieldPVCoordinates<T> extPVTopo = t.transformOnlyPV(extPV);

  319.         // Compute range rate (doppler) : relative rate along the line of sight
  320.         return FieldVector3D.dotProduct(extPVTopo.getPosition(), extPVTopo.getVelocity()).divide(
  321.                 extPVTopo.getPosition().getNorm());

  322.     }

  323.     /**
  324.      * Compute the limit visibility point for a satellite in a given direction.
  325.      * <p>
  326.      * This method can be used to compute visibility circles around ground stations
  327.      * for example, using a simple loop on azimuth, with either a fixed elevation
  328.      * or an elevation that depends on azimuth to take ground masks into account.
  329.      * </p>
  330.      * @param radius satellite distance to Earth center
  331.      * @param azimuth pointing azimuth from station
  332.      * @param elevation pointing elevation from station
  333.      * @return limit visibility point for the satellite
  334.      */
  335.     public GeodeticPoint computeLimitVisibilityPoint(final double radius,
  336.                                                      final double azimuth, final double elevation) {
  337.         try {
  338.             // convergence threshold on point position: 1mm
  339.             final double deltaP = 0.001;
  340.             final UnivariateSolver solver =
  341.                     new BracketingNthOrderBrentSolver(deltaP / Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
  342.                             deltaP, deltaP, 5);

  343.             // find the distance such that a point in the specified direction and at the solved-for
  344.             // distance is exactly at the specified radius
  345.             final double distance = solver.solve(1000, new UnivariateFunction() {
  346.                 /** {@inheritDoc} */
  347.                 public double value(final double x) {
  348.                     final GeodeticPoint gp = pointAtDistance(azimuth, elevation, x);
  349.                     return parentShape.transform(gp).getNorm() - radius;
  350.                 }
  351.             }, 0, 2 * radius);

  352.             // return the limit point
  353.             return pointAtDistance(azimuth, elevation, distance);

  354.         } catch (MathRuntimeException mrte) {
  355.             throw new OrekitException(mrte);
  356.         }
  357.     }

  358.     /** Compute the point observed from the station at some specified distance.
  359.      * @param azimuth pointing azimuth from station
  360.      * @param elevation pointing elevation from station
  361.      * @param distance distance to station
  362.      * @return observed point
  363.      */
  364.     public GeodeticPoint pointAtDistance(final double azimuth, final double elevation,
  365.                                          final double distance) {
  366.         final SinCos scAz  = FastMath.sinCos(azimuth);
  367.         final SinCos scEl  = FastMath.sinCos(elevation);
  368.         final Vector3D  observed = new Vector3D(distance * scEl.cos() * scAz.sin(),
  369.                 distance * scEl.cos() * scAz.cos(),
  370.                 distance * scEl.sin());
  371.         return parentShape.transform(observed, this, AbsoluteDate.ARBITRARY_EPOCH);
  372.     }

  373.     /** {@inheritDoc} */
  374.     @Override
  375.     public Vector3D getPosition(final AbsoluteDate date, final Frame frame) {
  376.         return getStaticTransformTo(frame, date).transformPosition(Vector3D.ZERO);
  377.     }

  378.     /** {@inheritDoc} */
  379.     @Override
  380.     public <T extends CalculusFieldElement<T>> FieldVector3D<T> getPosition(final FieldAbsoluteDate<T> date,
  381.                                                                             final Frame frame) {
  382.         return getStaticTransformTo(frame, date).transformPosition(Vector3D.ZERO);
  383.     }

  384.     /** {@inheritDoc} */
  385.     @Override
  386.     public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
  387.         return getTransformTo(frame, date).transformPVCoordinates(new TimeStampedPVCoordinates(date,
  388.                 Vector3D.ZERO,
  389.                 Vector3D.ZERO,
  390.                 Vector3D.ZERO));
  391.     }

  392.     /** {@inheritDoc} */
  393.     @Override
  394.     public <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> getPVCoordinates(final FieldAbsoluteDate<T> date,
  395.                                                                                                  final Frame frame) {
  396.         final FieldVector3D<T> zero = FieldVector3D.getZero(date.getField());
  397.         return getTransformTo(frame, date).transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(date, zero, zero, zero));
  398.     }

  399.     /** Get the topocentric position from {@link TrackingCoordinates}.
  400.      * @param coords The coordinates that are to be converted.
  401.      * @return The topocentric coordinates.
  402.      * @since 12.1
  403.      */
  404.     public static Vector3D getTopocentricPosition(final TrackingCoordinates coords) {
  405.         return getTopocentricPosition(coords.getAzimuth(), coords.getElevation(), coords.getRange());
  406.     }

  407.     /** Get the topocentric position from {@link FieldTrackingCoordinates}.
  408.      * @param coords The coordinates that are to be converted.
  409.      * @param <T> Type of the field coordinates.
  410.      * @return The topocentric coordinates.
  411.      * @since 12.1
  412.      */
  413.     public static <T extends CalculusFieldElement<T>> FieldVector3D<T> getTopocentricPosition(final FieldTrackingCoordinates<T> coords) {
  414.         return getTopocentricPosition(coords.getAzimuth(), coords.getElevation(), coords.getRange());
  415.     }

  416.     /**
  417.      * Gets the topocentric position from a set of az/el/ra coordinates.
  418.      * @param azimuth the angle of rotation around the vertical axis, going East.
  419.      * @param elevation the elevation angle from the local horizon.
  420.      * @param range the distance from the goedetic position.
  421.      * @return the topocentric position.
  422.      * @since 12.1
  423.      */
  424.     private static Vector3D getTopocentricPosition(final double azimuth, final double elevation, final double range) {
  425.         final SinCos sinCosAz = FastMath.sinCos(azimuth);
  426.         final SinCos sinCosEL = FastMath.sinCos(elevation);
  427.         return new Vector3D(range * sinCosEL.cos() * sinCosAz.sin(), range * sinCosEL.cos() * sinCosAz.cos(), range * sinCosEL.sin());
  428.     }

  429.     /**
  430.      * Gets the topocentric position from a set of az/el/ra coordinates.
  431.      * @param azimuth the angle of rotation around the vertical axis, going East.
  432.      * @param elevation the elevation angle from the local horizon.
  433.      * @param range the distance from the geodetic position.
  434.      * @return the topocentric position.
  435.      * @param <T> the type of the az/el/ra coordinates.
  436.      * @since 12.1
  437.      */
  438.     private static <T extends CalculusFieldElement<T>> FieldVector3D<T> getTopocentricPosition(final T azimuth, final T elevation, final T range) {
  439.         final FieldSinCos<T> sinCosAz = FastMath.sinCos(azimuth);
  440.         final FieldSinCos<T> sinCosEl = FastMath.sinCos(elevation);
  441.         return new FieldVector3D<>(
  442.                 range.multiply(sinCosEl.cos()).multiply(sinCosAz.sin()),
  443.                 range.multiply(sinCosEl.cos()).multiply(sinCosAz.cos()),
  444.                 range.multiply(sinCosEl.sin())
  445.         );
  446.     }

  447.     /** Transform point in topocentric frame.
  448.      * @param extPoint point
  449.      * @param date current date
  450.      * @param frame the frame where to define the position
  451.      * @return transformed point in topocentric frame
  452.      */
  453.     private Vector3D transformPoint(final Vector3D extPoint, final Frame frame, final AbsoluteDate date) {
  454.         final StaticTransform t = frame.getStaticTransformTo(this, date);
  455.         return t.transformPosition(extPoint);
  456.     }

  457.     /** Transform point in topocentric frame.
  458.      * @param <T> type of the field elements
  459.      * @param extPoint point
  460.      * @param date current date
  461.      * @param frame the frame where to define the position
  462.      * @return transformed point in topocentric frame
  463.      */
  464.     private <T extends CalculusFieldElement<T>> FieldVector3D<T> transformPoint(final FieldVector3D<T> extPoint,
  465.                                                                                 final Frame frame,
  466.                                                                                 final FieldAbsoluteDate<T> date) {
  467.         final FieldStaticTransform<T> t = frame.getStaticTransformTo(this, date);
  468.         return t.transformPosition(extPoint);
  469.     }

  470.     /** Compute azimuth from topocentric point.
  471.      * @param extPointTopo topocentric point
  472.      * @return azimuth
  473.      */
  474.     private double computeAzimuthFromTopoPoint(final Vector3D extPointTopo) {
  475.         final double azimuth = FastMath.atan2(extPointTopo.getX(), extPointTopo.getY());
  476.         if (azimuth < 0.0) {
  477.             return azimuth + MathUtils.TWO_PI;
  478.         } else {
  479.             return azimuth;
  480.         }
  481.     }

  482.     /** Compute azimuth from topocentric point.
  483.      * @param <T> type of the field elements
  484.      * @param extPointTopo topocentric point
  485.      * @return azimuth
  486.      */
  487.     private <T extends CalculusFieldElement<T>> T computeAzimuthFromTopoPoint(final FieldVector3D<T> extPointTopo) {
  488.         final T azimuth = FastMath.atan2(extPointTopo.getX(), extPointTopo.getY());
  489.         if (azimuth.getReal() < 0.0) {
  490.             return azimuth.add(MathUtils.TWO_PI);
  491.         } else {
  492.             return azimuth;
  493.         }
  494.     }

  495. }