/**
 * Implementation of the net.sf.geographiclib.GeodesicLine class
 *
 * Copyright (c) Charles Karney (2013-2020) <charles@karney.com> and licensed
 * under the MIT/X11 License.  For more information, see
 * https://geographiclib.sourceforge.io/
 **********************************************************************/
package net.sf.geographiclib;

/**
 * A geodesic line.
 * <p>
 * GeodesicLine facilitates the determination of a series of points on a single
 * geodesic.  The starting point (<i>lat1</i>, <i>lon1</i>) and the azimuth
 * <i>azi1</i> are specified in the constructor; alternatively, the {@link
 * Geodesic#Line Geodesic.Line} method can be used to create a GeodesicLine.
 * {@link #Position Position} returns the location of point 2 a distance
 * <i>s12</i> along the geodesic.  Alternatively {@link #ArcPosition
 * ArcPosition} gives the position of point 2 an arc length <i>a12</i> along
 * the geodesic.
 * <p>
 * You can register the position of a reference point 3 a distance (arc
 * length), <i>s13</i> (<i>a13</i>) along the geodesic with the
 * {@link #SetDistance SetDistance} ({@link #SetArc SetArc}) functions.  Points
 * a fractional distance along the line can be found by providing, for example,
 * 0.5 * {@link #Distance} as an argument to {@link #Position Position}.  The
 * {@link Geodesic#InverseLine Geodesic.InverseLine} or
 * {@link Geodesic#DirectLine Geodesic.DirectLine} methods return GeodesicLine
 * objects with point 3 set to the point 2 of the corresponding geodesic
 * problem.  GeodesicLine objects created with the public constructor or with
 * {@link Geodesic#Line Geodesic.Line} have <i>s13</i> and <i>a13</i> set to
 * NaNs.
 * <p>
 * The calculations are accurate to better than 15 nm (15 nanometers).  See
 * Sec. 9 of
 * <a href="https://arxiv.org/abs/1102.1215v1">arXiv:1102.1215v1</a> for
 * details.  The algorithms used by this class are based on series expansions
 * using the flattening <i>f</i> as a small parameter.  These are only accurate
 * for |<i>f</i>| &lt; 0.02; however reasonably accurate results will be
 * obtained for |<i>f</i>| &lt; 0.2.
 * <p>
 * The algorithms are described in
 * <ul>
 * <li>
 *   C. F. F. Karney,
 *   <a href="https://doi.org/10.1007/s00190-012-0578-z">
 *   Algorithms for geodesics</a>,
 *   J. Geodesy <b>87</b>, 43&ndash;55 (2013)
 *   (<a href="https://geographiclib.sourceforge.io/geod-addenda.html">addenda</a>).
 * </ul>
 * <p>
 * Here's an example of using this class
 * <pre>
 * {@code
 * import net.sf.geographiclib.*;
 * public class GeodesicLineTest {
 *   public static void main(String[] args) {
 *     // Print waypoints between JFK and SIN
 *     Geodesic geod = Geodesic.WGS84;
 *     double
 *       lat1 = 40.640, lon1 = -73.779, // JFK
 *       lat2 =  1.359, lon2 = 103.989; // SIN
 *     GeodesicLine line = geod.InverseLine(lat1, lon1, lat2, lon2,
 *                                          GeodesicMask.DISTANCE_IN |
 *                                          GeodesicMask.LATITUDE |
 *                                          GeodesicMask.LONGITUDE);
 *     double ds0 = 500e3;     // Nominal distance between points = 500 km
 *     // The number of intervals
 *     int num = (int)(Math.ceil(line.Distance() / ds0));
 *     {
 *       // Use intervals of equal length
 *       double ds = line.Distance() / num;
 *       for (int i = 0; i <= num; ++i) {
 *         GeodesicData g = line.Position(i * ds,
 *                                        GeodesicMask.LATITUDE |
 *                                        GeodesicMask.LONGITUDE);
 *         System.out.println(i + " " + g.lat2 + " " + g.lon2);
 *       }
 *     }
 *     {
 *       // Slightly faster, use intervals of equal arc length
 *       double da = line.Arc() / num;
 *       for (int i = 0; i <= num; ++i) {
 *         GeodesicData g = line.ArcPosition(i * da,
 *                                           GeodesicMask.LATITUDE |
 *                                           GeodesicMask.LONGITUDE);
 *         System.out.println(i + " " + g.lat2 + " " + g.lon2);
 *       }
 *     }
 *   }
 * }}</pre>
 **********************************************************************/
public class GeodesicLine {

  private static final int nC1_ = Geodesic.nC1_;
  private static final int nC1p_ = Geodesic.nC1p_;
  private static final int nC2_ = Geodesic.nC2_;
  private static final int nC3_ = Geodesic.nC3_;
  private static final int nC4_ = Geodesic.nC4_;

  private double _lat1, _lon1, _azi1;
  private double _a, _f, _b, _c2, _f1, _salp0, _calp0, _k2,
    _salp1, _calp1, _ssig1, _csig1, _dn1, _stau1, _ctau1, _somg1, _comg1,
    _A1m1, _A2m1, _A3c, _B11, _B21, _B31, _A4, _B41;
  private double _a13, _s13;
  // index zero elements of _C1a, _C1pa, _C2a, _C3a are unused
  private double _C1a[], _C1pa[], _C2a[], _C3a[],
    _C4a[];    // all the elements of _C4a are used
  private int _caps;

  /**
   * Constructor for a geodesic line staring at latitude <i>lat1</i>, longitude
   * <i>lon1</i>, and azimuth <i>azi1</i> (all in degrees).
   * <p>
   * @param g A {@link Geodesic} object used to compute the necessary
   *   information about the GeodesicLine.
   * @param lat1 latitude of point 1 (degrees).
   * @param lon1 longitude of point 1 (degrees).
   * @param azi1 azimuth at point 1 (degrees).
   * <p>
   * <i>lat1</i> should be in the range [&minus;90&deg;, 90&deg;].
   * <p>
   * If the point is at a pole, the azimuth is defined by keeping <i>lon1</i>
   * fixed, writing <i>lat1</i> = &plusmn;(90&deg; &minus; &epsilon;), and
   * taking the limit &epsilon; &rarr; 0+.
   **********************************************************************/
  public GeodesicLine(Geodesic g,
                      double lat1, double lon1, double azi1) {
    this(g, lat1, lon1, azi1, GeodesicMask.ALL);
  }

  /**
   * Constructor for a geodesic line staring at latitude <i>lat1</i>, longitude
   * <i>lon1</i>, and azimuth <i>azi1</i> (all in degrees) with a subset of the
   * capabilities included.
   * <p>
   * @param g A {@link Geodesic} object used to compute the necessary
   *   information about the GeodesicLine.
   * @param lat1 latitude of point 1 (degrees).
   * @param lon1 longitude of point 1 (degrees).
   * @param azi1 azimuth at point 1 (degrees).
   * @param caps bitor'ed combination of {@link GeodesicMask} values
   *   specifying the capabilities the GeodesicLine object should possess,
   *   i.e., which quantities can be returned in calls to {@link #Position
   *   Position}.
   * <p>
   * The {@link GeodesicMask} values are
   * <ul>
   * <li>
   *   <i>caps</i> |= {@link GeodesicMask#LATITUDE} for the latitude
   *   <i>lat2</i>; this is added automatically;
   * <li>
   *   <i>caps</i> |= {@link GeodesicMask#LONGITUDE} for the latitude
   *   <i>lon2</i>;
   * <li>
   *   <i>caps</i> |= {@link GeodesicMask#AZIMUTH} for the latitude
   *   <i>azi2</i>; this is added automatically;
   * <li>
   *   <i>caps</i> |= {@link GeodesicMask#DISTANCE} for the distance
   *   <i>s12</i>;
   * <li>
   *   <i>caps</i> |= {@link GeodesicMask#REDUCEDLENGTH} for the reduced length
   *   <i>m12</i>;
   * <li>
   *   <i>caps</i> |= {@link GeodesicMask#GEODESICSCALE} for the geodesic
   *   scales <i>M12</i> and <i>M21</i>;
   * <li>
   *   <i>caps</i> |= {@link GeodesicMask#AREA} for the area <i>S12</i>;
   * <li>
   *   <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN} permits the length of
   *   the geodesic to be given in terms of <i>s12</i>; without this capability
   *   the length can only be specified in terms of arc length;
   * <li>
   *   <i>caps</i> |= {@link GeodesicMask#ALL} for all of the above.
   * </ul>
   **********************************************************************/
  public GeodesicLine(Geodesic g,
                      double lat1, double lon1, double azi1,
                      int caps) {
    azi1 = GeoMath.AngNormalize(azi1);
    double salp1, calp1;
    Pair p = new Pair();
    // Guard against underflow in salp0
    GeoMath.sincosd(p, GeoMath.AngRound(azi1));
    salp1 = p.first; calp1 = p.second;
    LineInit(g, lat1, lon1, azi1, salp1, calp1, caps, p);
  }

  private void LineInit(Geodesic g,
                        double lat1, double lon1,
                        double azi1, double salp1, double calp1,
                        int caps, Pair p) {
    _a = g._a;
    _f = g._f;
    _b = g._b;
    _c2 = g._c2;
    _f1 = g._f1;
    // Always allow latitude and azimuth and unrolling the longitude
    _caps = caps | GeodesicMask.LATITUDE | GeodesicMask.AZIMUTH |
      GeodesicMask.LONG_UNROLL;

    _lat1 = GeoMath.LatFix(lat1);
    _lon1 = lon1;
    _azi1 = azi1; _salp1 = salp1; _calp1 = calp1;
    double cbet1, sbet1;
    GeoMath.sincosd(p, GeoMath.AngRound(_lat1));
    sbet1 = _f1 * p.first; cbet1 = p.second;
    // Ensure cbet1 = +epsilon at poles
    GeoMath.norm(p, sbet1, cbet1);
    sbet1 = p.first; cbet1 = Math.max(Geodesic.tiny_, p.second);
    _dn1 = Math.sqrt(1 + g._ep2 * GeoMath.sq(sbet1));

    // Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0),
    _salp0 = _salp1 * cbet1; // alp0 in [0, pi/2 - |bet1|]
    // Alt: calp0 = Math.hypot(sbet1, calp1 * cbet1).  The following
    // is slightly better (consider the case salp1 = 0).
    _calp0 = Math.hypot(_calp1, _salp1 * sbet1);
    // Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1).
    // sig = 0 is nearest northward crossing of equator.
    // With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line).
    // With bet1 =  pi/2, alp1 = -pi, sig1 =  pi/2
    // With bet1 = -pi/2, alp1 =  0 , sig1 = -pi/2
    // Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1).
    // With alp0 in (0, pi/2], quadrants for sig and omg coincide.
    // No atan2(0,0) ambiguity at poles since cbet1 = +epsilon.
    // With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi.
    _ssig1 = sbet1; _somg1 = _salp0 * sbet1;
    _csig1 = _comg1 = sbet1 != 0 || _calp1 != 0 ? cbet1 * _calp1 : 1;
    GeoMath.norm(p, _ssig1, _csig1);
    _ssig1 = p.first; _csig1 = p.second; // sig1 in (-pi, pi]
    // GeoMath.norm(_somg1, _comg1); -- don't need to normalize!

    _k2 = GeoMath.sq(_calp0) * g._ep2;
    double eps = _k2 / (2 * (1 + Math.sqrt(1 + _k2)) + _k2);

    if ((_caps & GeodesicMask.CAP_C1) != 0) {
      _A1m1 = Geodesic.A1m1f(eps);
      _C1a = new double[nC1_ + 1];
      Geodesic.C1f(eps, _C1a);
      _B11 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C1a);
      double s = Math.sin(_B11), c = Math.cos(_B11);
      // tau1 = sig1 + B11
      _stau1 = _ssig1 * c + _csig1 * s;
      _ctau1 = _csig1 * c - _ssig1 * s;
      // Not necessary because C1pa reverts C1a
      //    _B11 = -SinCosSeries(true, _stau1, _ctau1, _C1pa, nC1p_);
    }

    if ((_caps & GeodesicMask.CAP_C1p) != 0) {
      _C1pa = new double[nC1p_ + 1];
      Geodesic.C1pf(eps, _C1pa);
    }

    if ((_caps & GeodesicMask.CAP_C2) != 0) {
      _C2a = new double[nC2_ + 1];
      _A2m1 = Geodesic.A2m1f(eps);
      Geodesic.C2f(eps, _C2a);
      _B21 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C2a);
    }

    if ((_caps & GeodesicMask.CAP_C3) != 0) {
      _C3a = new double[nC3_];
      g.C3f(eps, _C3a);
      _A3c = -_f * _salp0 * g.A3f(eps);
      _B31 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C3a);
    }

    if ((_caps & GeodesicMask.CAP_C4) != 0) {
      _C4a = new double[nC4_];
      g.C4f(eps, _C4a);
      // Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0)
      _A4 = GeoMath.sq(_a) * _calp0 * _salp0 * g._e2;
      _B41 = Geodesic.SinCosSeries(false, _ssig1, _csig1, _C4a);
    }
  }

  protected GeodesicLine(Geodesic g,
                         double lat1, double lon1,
                         double azi1, double salp1, double calp1,
                         int caps, boolean arcmode, double s13_a13) {
    Pair p = new Pair();
    LineInit(g, lat1, lon1, azi1, salp1, calp1, caps, p);
    GenSetDistance(arcmode, s13_a13);
  }

  /**
   * A default constructor.  If GeodesicLine.Position is called on the
   * resulting object, it returns immediately (without doing any calculations).
   * The object can be set with a call to {@link Geodesic.Line}.  Use {@link
   * Init()} to test whether object is still in this uninitialized state.
   * (This constructor was useful in C++, e.g., to allow vectors of
   * GeodesicLine objects.  It may not be needed in Java, so make it private.)
   **********************************************************************/
  private GeodesicLine() { _caps = 0; }

  /**
   * Compute the position of point 2 which is a distance <i>s12</i> (meters)
   * from point 1.
   * <p>
   * @param s12 distance from point 1 to point 2 (meters); it can be
   *   negative.
   * @return a {@link GeodesicData} object with the following fields:
   *   <i>lat1</i>, <i>lon1</i>, <i>azi1</i>, <i>lat2</i>, <i>lon2</i>,
   *   <i>azi2</i>, <i>s12</i>, <i>a12</i>.  Some of these results may be
   *   missing if the GeodesicLine did not include the relevant capability.
   * <p>
   * The values of <i>lon2</i> and <i>azi2</i> returned are in the range
   * [&minus;180&deg;, 180&deg;].
   * <p>
   * The GeodesicLine object <i>must</i> have been constructed with <i>caps</i>
   * |= {@link GeodesicMask#DISTANCE_IN}; otherwise no parameters are set.
   **********************************************************************/
  public GeodesicData Position(double s12) {
    return Position(false, s12, GeodesicMask.STANDARD);
  }
  /**
   * Compute the position of point 2 which is a distance <i>s12</i> (meters)
   * from point 1 and with a subset of the geodesic results returned.
   * <p>
   * @param s12 distance from point 1 to point 2 (meters); it can be
   *   negative.
   * @param outmask a bitor'ed combination of {@link GeodesicMask} values
   *   specifying which results should be returned.
   * @return a {@link GeodesicData} object including the requested results.
   * <p>
   * The GeodesicLine object <i>must</i> have been constructed with <i>caps</i>
   * |= {@link GeodesicMask#DISTANCE_IN}; otherwise no parameters are set.
   * Requesting a value which the GeodesicLine object is not capable of
   * computing is not an error (no parameters will be set).  The value of
   * <i>lon2</i> returned is normally in the range [&minus;180&deg;, 180&deg;];
   * however if the <i>outmask</i> includes the
   * {@link GeodesicMask#LONG_UNROLL} flag, the longitude is "unrolled" so that
   * the quantity <i>lon2</i> &minus; <i>lon1</i> indicates how many times and
   * in what sense the geodesic encircles the ellipsoid.
   **********************************************************************/
  public GeodesicData Position(double s12, int outmask) {
    return Position(false, s12, outmask);
  }

  /**
   * Compute the position of point 2 which is an arc length <i>a12</i>
   * (degrees) from point 1.
   * <p>
   * @param a12 arc length from point 1 to point 2 (degrees); it can
   *   be negative.
   * @return a {@link GeodesicData} object with the following fields:
   *   <i>lat1</i>, <i>lon1</i>, <i>azi1</i>, <i>lat2</i>, <i>lon2</i>,
   *   <i>azi2</i>, <i>s12</i>, <i>a12</i>.  Some of these results may be
   *   missing if the GeodesicLine did not include the relevant capability.
   * <p>
   * The values of <i>lon2</i> and <i>azi2</i> returned are in the range
   * [&minus;180&deg;, 180&deg;].
   * <p>
   * The GeodesicLine object <i>must</i> have been constructed with <i>caps</i>
   * |= {@link GeodesicMask#DISTANCE_IN}; otherwise no parameters are set.
   **********************************************************************/
  public GeodesicData ArcPosition(double a12) {
    return Position(true, a12, GeodesicMask.STANDARD);
  }
  /**
   * Compute the position of point 2 which is an arc length <i>a12</i>
   * (degrees) from point 1 and with a subset of the geodesic results returned.
   * <p>
   * @param a12 arc length from point 1 to point 2 (degrees); it can
   *   be negative.
   * @param outmask a bitor'ed combination of {@link GeodesicMask} values
   *   specifying which results should be returned.
   * @return a {@link GeodesicData} object giving <i>lat1</i>, <i>lon2</i>,
   *   <i>azi2</i>, and <i>a12</i>.
   * <p>
   * Requesting a value which the GeodesicLine object is not capable of
   * computing is not an error (no parameters will be set).  The value of
   * <i>lon2</i> returned is in the range [&minus;180&deg;, 180&deg;], unless
   * the <i>outmask</i> includes the {@link GeodesicMask#LONG_UNROLL} flag.
   **********************************************************************/
  public GeodesicData ArcPosition(double a12, int outmask) {
    return Position(true, a12, outmask);
  }

  /**
   * The general position function.  {@link #Position(double, int) Position}
   * and {@link #ArcPosition(double, int) ArcPosition} are defined in terms of
   * this function.
   * <p>
   * @param arcmode boolean flag determining the meaning of the second
   *   parameter; if arcmode is false, then the GeodesicLine object must have
   *   been constructed with <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN}.
   * @param s12_a12 if <i>arcmode</i> is false, this is the distance between
   *   point 1 and point 2 (meters); otherwise it is the arc length between
   *   point 1 and point 2 (degrees); it can be negative.
   * @param outmask a bitor'ed combination of {@link GeodesicMask} values
   *   specifying which results should be returned.
   * @return a {@link GeodesicData} object with the requested results.
   * <p>
   * The {@link GeodesicMask} values possible for <i>outmask</i> are
   * <ul>
   * <li>
   *   <i>outmask</i> |= {@link GeodesicMask#LATITUDE} for the latitude
   *   <i>lat2</i>;
   * <li>
   *   <i>outmask</i> |= {@link GeodesicMask#LONGITUDE} for the latitude
   *   <i>lon2</i>;
   * <li>
   *   <i>outmask</i> |= {@link GeodesicMask#AZIMUTH} for the latitude
   *   <i>azi2</i>;
   * <li>
   *   <i>outmask</i> |= {@link GeodesicMask#DISTANCE} for the distance
   *   <i>s12</i>;
   * <li>
   *   <i>outmask</i> |= {@link GeodesicMask#REDUCEDLENGTH} for the reduced
   *   length <i>m12</i>;
   * <li>
   *   <i>outmask</i> |= {@link GeodesicMask#GEODESICSCALE} for the geodesic
   *   scales <i>M12</i> and <i>M21</i>;
   * <li>
   *   <i>outmask</i> |= {@link GeodesicMask#ALL} for all of the above;
   * <li>
   *   <i>outmask</i> |= {@link GeodesicMask#LONG_UNROLL} to unroll <i>lon2</i>
   *   (instead of reducing it to the range [&minus;180&deg;, 180&deg;]).
   * </ul>
   * <p>
   * Requesting a value which the GeodesicLine object is not capable of
   * computing is not an error; Double.NaN is returned instead.
   **********************************************************************/
  public GeodesicData Position(boolean arcmode, double s12_a12,
                               int outmask) {
    outmask &= _caps & GeodesicMask.OUT_MASK;
    GeodesicData r = new GeodesicData();
    if (!( Init() &&
           (arcmode ||
            (_caps & (GeodesicMask.OUT_MASK & GeodesicMask.DISTANCE_IN)) != 0)
           ))
      // Uninitialized or impossible distance calculation requested
      return r;
    r.lat1 = _lat1; r.azi1 = _azi1;
    r.lon1 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) ? _lon1 :
      GeoMath.AngNormalize(_lon1);

    // Avoid warning about uninitialized B12.
    double sig12, ssig12, csig12, B12 = 0, AB1 = 0;
    if (arcmode) {
      // Interpret s12_a12 as spherical arc length
      r.a12 = s12_a12;
      sig12 = Math.toRadians(s12_a12);
      Pair p = new Pair();
      GeoMath.sincosd(p, s12_a12); ssig12 = p.first; csig12 = p.second;
    } else {
      // Interpret s12_a12 as distance
      r.s12 = s12_a12;
      double
        tau12 = s12_a12 / (_b * (1 + _A1m1)),
        s = Math.sin(tau12),
        c = Math.cos(tau12);
      // tau2 = tau1 + tau12
      B12 = - Geodesic.SinCosSeries(true,
                                    _stau1 * c + _ctau1 * s,
                                    _ctau1 * c - _stau1 * s,
                                    _C1pa);
      sig12 = tau12 - (B12 - _B11);
      ssig12 = Math.sin(sig12); csig12 = Math.cos(sig12);
      if (Math.abs(_f) > 0.01) {
        // Reverted distance series is inaccurate for |f| > 1/100, so correct
        // sig12 with 1 Newton iteration.  The following table shows the
        // approximate maximum error for a = WGS_a() and various f relative to
        // GeodesicExact.
        //     erri = the error in the inverse solution (nm)
        //     errd = the error in the direct solution (series only) (nm)
        //     errda = the error in the direct solution
        //             (series + 1 Newton) (nm)
        //
        //       f     erri  errd errda
        //     -1/5    12e6 1.2e9  69e6
        //     -1/10  123e3  12e6 765e3
        //     -1/20   1110 108e3  7155
        //     -1/50  18.63 200.9 27.12
        //     -1/100 18.63 23.78 23.37
        //     -1/150 18.63 21.05 20.26
        //      1/150 22.35 24.73 25.83
        //      1/100 22.35 25.03 25.31
        //      1/50  29.80 231.9 30.44
        //      1/20   5376 146e3  10e3
        //      1/10  829e3  22e6 1.5e6
        //      1/5   157e6 3.8e9 280e6
        double
          ssig2 = _ssig1 * csig12 + _csig1 * ssig12,
          csig2 = _csig1 * csig12 - _ssig1 * ssig12;
        B12 = Geodesic.SinCosSeries(true, ssig2, csig2, _C1a);
        double serr = (1 + _A1m1) * (sig12 + (B12 - _B11)) - s12_a12 / _b;
        sig12 = sig12 - serr / Math.sqrt(1 + _k2 * GeoMath.sq(ssig2));
        ssig12 = Math.sin(sig12); csig12 = Math.cos(sig12);
        // Update B12 below
      }
      r.a12 = Math.toDegrees(sig12);
    }

    double ssig2, csig2, sbet2, cbet2, salp2, calp2;
    // sig2 = sig1 + sig12
    ssig2 = _ssig1 * csig12 + _csig1 * ssig12;
    csig2 = _csig1 * csig12 - _ssig1 * ssig12;
    double dn2 = Math.sqrt(1 + _k2 * GeoMath.sq(ssig2));
    if ((outmask & (GeodesicMask.DISTANCE | GeodesicMask.REDUCEDLENGTH |
                    GeodesicMask.GEODESICSCALE)) != 0) {
      if (arcmode || Math.abs(_f) > 0.01)
        B12 = Geodesic.SinCosSeries(true, ssig2, csig2, _C1a);
      AB1 = (1 + _A1m1) * (B12 - _B11);
    }
    // sin(bet2) = cos(alp0) * sin(sig2)
    sbet2 = _calp0 * ssig2;
    // Alt: cbet2 = Math.hypot(csig2, salp0 * ssig2);
    cbet2 = Math.hypot(_salp0, _calp0 * csig2);
    if (cbet2 == 0)
      // I.e., salp0 = 0, csig2 = 0.  Break the degeneracy in this case
      cbet2 = csig2 = Geodesic.tiny_;
    // tan(alp0) = cos(sig2)*tan(alp2)
    salp2 = _salp0; calp2 = _calp0 * csig2; // No need to normalize

    if ((outmask & GeodesicMask.DISTANCE) != 0 && arcmode)
      r.s12 = _b * ((1 + _A1m1) * sig12 + AB1);

    if ((outmask & GeodesicMask.LONGITUDE) != 0) {
      // tan(omg2) = sin(alp0) * tan(sig2)
      double somg2 = _salp0 * ssig2, comg2 = csig2, // No need to normalize
        E = Math.copySign(1, _salp0);               // east or west going?
      // omg12 = omg2 - omg1
      double omg12 = ((outmask & GeodesicMask.LONG_UNROLL) != 0)
        ? E * (sig12
               - (Math.atan2(  ssig2, csig2) - Math.atan2(  _ssig1, _csig1))
               + (Math.atan2(E*somg2, comg2) - Math.atan2(E*_somg1, _comg1)))
        : Math.atan2(somg2 * _comg1 - comg2 * _somg1,
                     comg2 * _comg1 + somg2 * _somg1);
      double lam12 = omg12 + _A3c *
        ( sig12 + (Geodesic.SinCosSeries(true, ssig2, csig2, _C3a)
                   - _B31));
      double lon12 = Math.toDegrees(lam12);
      r.lon2 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) ? _lon1 + lon12 :
        GeoMath.AngNormalize(r.lon1 + GeoMath.AngNormalize(lon12));
    }

    if ((outmask & GeodesicMask.LATITUDE) != 0)
      r.lat2 = GeoMath.atan2d(sbet2, _f1 * cbet2);

    if ((outmask & GeodesicMask.AZIMUTH) != 0)
      r.azi2 = GeoMath.atan2d(salp2, calp2);

    if ((outmask &
         (GeodesicMask.REDUCEDLENGTH | GeodesicMask.GEODESICSCALE)) != 0) {
      double
        B22 = Geodesic.SinCosSeries(true, ssig2, csig2, _C2a),
        AB2 = (1 + _A2m1) * (B22 - _B21),
        J12 = (_A1m1 - _A2m1) * sig12 + (AB1 - AB2);
      if ((outmask & GeodesicMask.REDUCEDLENGTH) != 0)
        // Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure
        // accurate cancellation in the case of coincident points.
        r.m12 = _b * ((dn2 * (_csig1 * ssig2) - _dn1 * (_ssig1 * csig2))
                    - _csig1 * csig2 * J12);
      if ((outmask & GeodesicMask.GEODESICSCALE) != 0) {
        double t = _k2 * (ssig2 - _ssig1) * (ssig2 + _ssig1) / (_dn1 + dn2);
        r.M12 = csig12 + (t *  ssig2 -  csig2 * J12) * _ssig1 / _dn1;
        r.M21 = csig12 - (t * _ssig1 - _csig1 * J12) *  ssig2 /  dn2;
      }
    }

    if ((outmask & GeodesicMask.AREA) != 0) {
      double
        B42 = Geodesic.SinCosSeries(false, ssig2, csig2, _C4a);
      double salp12, calp12;
      if (_calp0 == 0 || _salp0 == 0) {
        // alp12 = alp2 - alp1, used in atan2 so no need to normalize
        salp12 = salp2 * _calp1 - calp2 * _salp1;
        calp12 = calp2 * _calp1 + salp2 * _salp1;
      } else {
        // tan(alp) = tan(alp0) * sec(sig)
        // tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1)
        // = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2)
        // If csig12 > 0, write
        //   csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1)
        // else
        //   csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1
        // No need to normalize
        salp12 = _calp0 * _salp0 *
          (csig12 <= 0 ? _csig1 * (1 - csig12) + ssig12 * _ssig1 :
           ssig12 * (_csig1 * ssig12 / (1 + csig12) + _ssig1));
        calp12 = GeoMath.sq(_salp0) + GeoMath.sq(_calp0) * _csig1 * csig2;
      }
      r.S12 = _c2 * Math.atan2(salp12, calp12) + _A4 * (B42 - _B41);
    }

    return r;
  }

  /**
   * Specify position of point 3 in terms of distance.
   *
   * @param s13 the distance from point 1 to point 3 (meters); it
   *   can be negative.
   *
   * This is only useful if the GeodesicLine object has been constructed
   * with <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN}.
   **********************************************************************/
  public void SetDistance(double s13) {
    _s13 = s13;
    GeodesicData g = Position(false, _s13, 0);
    _a13 = g.a12;
  }

  /**
   * Specify position of point 3 in terms of arc length.
   *
   * @param a13 the arc length from point 1 to point 3 (degrees); it
   *   can be negative.
   *
   * The distance <i>s13</i> is only set if the GeodesicLine object has been
   * constructed with <i>caps</i> |= {@link GeodesicMask#DISTANCE}.
   **********************************************************************/
  void SetArc(double a13) {
    _a13 = a13;
    GeodesicData g = Position(true, _a13, GeodesicMask.DISTANCE);
    _s13 = g.s12;
  }

  /**
   * Specify position of point 3 in terms of either distance or arc length.
   *
   * @param arcmode boolean flag determining the meaning of the second
   *   parameter; if <i>arcmode</i> is false, then the GeodesicLine object must
   *   have been constructed with <i>caps</i> |=
   *   {@link GeodesicMask#DISTANCE_IN}.
   * @param s13_a13 if <i>arcmode</i> is false, this is the distance from
   *   point 1 to point 3 (meters); otherwise it is the arc length from
   *   point 1 to point 3 (degrees); it can be negative.
   **********************************************************************/
  public void GenSetDistance(boolean arcmode, double s13_a13) {
    if (arcmode)
      SetArc(s13_a13);
    else
      SetDistance(s13_a13);
  }

  /**
   * @return true if the object has been initialized.
   **********************************************************************/
  private boolean Init() { return _caps != 0; }

  /**
   * @return <i>lat1</i> the latitude of point 1 (degrees).
   **********************************************************************/
  public double Latitude()
  { return Init() ? _lat1 : Double.NaN; }

  /**
   * @return <i>lon1</i> the longitude of point 1 (degrees).
   **********************************************************************/
  public double Longitude()
  { return Init() ? _lon1 : Double.NaN; }

  /**
   * @return <i>azi1</i> the azimuth (degrees) of the geodesic line at point 1.
   **********************************************************************/
  public double Azimuth()
  { return Init() ? _azi1 : Double.NaN; }

  /**
   * @return pair of sine and cosine of <i>azi1</i> the azimuth (degrees) of
   *   the geodesic line at point 1.
   **********************************************************************/
  public Pair AzimuthCosines() {
    return new Pair(Init() ? _salp1 : Double.NaN,
                    Init() ? _calp1 : Double.NaN);
  }

  /**
   * @return <i>azi0</i> the azimuth (degrees) of the geodesic line as it
   *   crosses the equator in a northward direction.
   **********************************************************************/
  public double EquatorialAzimuth() {
    return Init() ?
      GeoMath.atan2d(_salp0, _calp0) : Double.NaN;
  }

  /**
   * @return pair of sine and cosine of <i>azi0</i> the azimuth of the geodesic
   *   line as it crosses the equator in a northward direction.
   **********************************************************************/
  public Pair EquatorialAzimuthCosines() {
    return new Pair(Init() ? _salp0 : Double.NaN,
                    Init() ? _calp0 : Double.NaN);
  }

  /**
   * @return <i>a1</i> the arc length (degrees) between the northward
   *   equatorial crossing and point 1.
   **********************************************************************/
  public double EquatorialArc() {
    return Init() ?
      GeoMath.atan2d(_ssig1, _csig1) : Double.NaN;
  }

  /**
   * @return <i>a</i> the equatorial radius of the ellipsoid (meters).  This is
   *   the value inherited from the Geodesic object used in the constructor.
   **********************************************************************/
  public double EquatorialRadius()
  { return Init() ? _a : Double.NaN; }

  /**
   * @return <i>f</i> the flattening of the ellipsoid.  This is the value
   *   inherited from the Geodesic object used in the constructor.
   **********************************************************************/
  public double Flattening()
  { return Init() ? _f : Double.NaN; }

  /**
   * @return <i>caps</i> the computational capabilities that this object was
   *   constructed with.  LATITUDE and AZIMUTH are always included.
   **********************************************************************/
  public int Capabilities() { return _caps; }

  /**
   * @param testcaps a set of bitor'ed {@link GeodesicMask} values.
   * @return true if the GeodesicLine object has all these capabilities.
   **********************************************************************/
  public boolean Capabilities(int testcaps) {
    testcaps &= GeodesicMask.OUT_ALL;
    return (_caps & testcaps) == testcaps;
  }

  /**
   * The distance or arc length to point 3.
   *
   * @param arcmode boolean flag determining the meaning of returned
   *   value.
   * @return <i>s13</i> if <i>arcmode</i> is false; <i>a13</i> if
   *   <i>arcmode</i> is true.
   **********************************************************************/
  public double GenDistance(boolean arcmode)
  { return Init() ? (arcmode ? _a13 : _s13) : Double.NaN; }

  /**
   * @return <i>s13</i>, the distance to point 3 (meters).
   **********************************************************************/
  public double Distance() { return GenDistance(false); }

  /**
   * @return <i>a13</i>, the arc length to point 3 (degrees).
   **********************************************************************/
  public double Arc() { return GenDistance(true); }

  /**
   * @deprecated An old name for {@link #EquatorialRadius()}.
   * @return <i>a</i> the equatorial radius of the ellipsoid (meters).
   **********************************************************************/
  @Deprecated
  public double MajorRadius() { return EquatorialRadius(); }
}
