Ejemplo n.º 1
0
        private void Lengths(EllipticFunction E,
                             double sig12,
                             double ssig1, double csig1, double dn1,
                             double ssig2, double csig2, double dn2,
                             double cbet1, double cbet2, GeodesicFlags outmask,
                             out double s12b, out double m12b, out double m0,
                             out double M12, out double M21)
        {
            // Return m12b = (reduced length)/_b; also calculate s12b = distance/_b,
            // and m0 = coefficient of secular term in expression for reduced length.
            s12b = m12b = m0 = M12 = M21 = double.NaN;

            outmask = outmask.Flags();
            // outmask & DISTANCE: set s12b
            // outmask & REDUCEDLENGTH: set m12b & m0
            // outmask & GEODESICSCALE: set M12 & M21

            // It's OK to have repeated dummy arguments,
            // e.g., s12b = m0 = M12 = M21 = dummy

            if (outmask.HasAny(GeodesicFlags.Distance))
            {
                // Missing a factor of _b
                s12b = E.E() / (PI / 2) *
                       (sig12 + (E.DeltaE(ssig2, csig2, dn2) - E.DeltaE(ssig1, csig1, dn1)));
            }
            if (outmask.HasAny(GeodesicFlags.ReducedLength | GeodesicFlags.GeodesicScale))
            {
                double
                    m0x = -E.K2 * E.D() / (PI / 2),
                    J12 = m0x *
                          (sig12 + (E.DeltaD(ssig2, csig2, dn2) - E.DeltaD(ssig1, csig1, dn1)));
                if (outmask.HasAny(GeodesicFlags.ReducedLength))
                {
                    m0 = m0x;
                    // Missing a factor of _b.  Add parens around (csig1 * ssig2) and
                    // (ssig1 * csig2) to ensure accurate cancellation in the case of
                    // coincident points.
                    m12b = dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) -
                           csig1 * csig2 * J12;
                }
                if (outmask.HasAny(GeodesicFlags.GeodesicScale))
                {
                    var csig12 = csig1 * csig2 + ssig1 * ssig2;
                    var t      = _ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2);
                    M12 = csig12 + (t * ssig2 - csig2 * J12) * ssig1 / dn1;
                    M21 = csig12 - (t * ssig1 - csig1 * J12) * ssig2 / dn2;
                }
            }
        }
Ejemplo n.º 2
0
        /// <inheritdoc/>
        public override double GenInverse(double lat1, double lon1, double lat2, double lon2,
                                          GeodesicFlags outmask,
                                          out double s12, out double azi1, out double azi2,
                                          out double m12, out double M12, out double M21, out double S12)
        {
            outmask = outmask.Flags();
            var a12 = GenInverse(lat1, lon1, lat2, lon2,
                                 outmask, out s12, out var salp1, out var calp1, out var salp2, out var calp2,
                                 out m12, out M12, out M21, out S12);

            if (outmask.HasAny(GeodesicFlags.Azimuth))
            {
                azi1 = Atan2d(salp1, calp1);
                azi2 = Atan2d(salp2, calp2);
            }
            else
            {
                azi1 = azi2 = double.NaN;
            }

            return(a12);
        }
Ejemplo n.º 3
0
        /// <inheritdoc/>
        public override double GenPosition(bool arcmode, double s12_a12, GeodesicFlags outmask,
                                           out double lat2, out double lon2, out double azi2,
                                           out double s12, out double m12, out double M12, out double M21,
                                           out double S12)
        {
            lat2     = lon2 = azi2 = s12 = m12 = M12 = M21 = S12 = double.NaN;
            outmask &= _caps & (GeodesicFlags)GeodesicCapability.OutMask;
            if (!(arcmode || _caps.HasAny(GeodesicFlags.DistanceIn)))
            {
                // Uninitialized or impossible distance calculation requested
                return(double.NaN);
            }

            // Avoid warning about uninitialized B12.
            double sig12, ssig12, csig12, B12 = 0, AB1 = 0;

            if (arcmode)
            {
                // Interpret s12_a12 as spherical arc length
                sig12 = s12_a12 * Degree;
                SinCosd(s12_a12, out ssig12, out csig12);
            }
            else
            {
                // Interpret s12_a12 as distance
                double
                    tau12 = s12_a12 / (_b * (1 + _A1m1)),
                    s     = Sin(tau12),
                    c     = Cos(tau12);
                // tau2 = tau1 + tau12
                B12 = -Geodesic.SinCosSeries(true,
                                             _stau1 * c + _ctau1 * s,
                                             _ctau1 * c - _stau1 * s,
                                             _C1pa.Span, nC1p_);
                sig12  = tau12 - (B12 - _B11);
                ssig12 = Sin(sig12); csig12 = Cos(sig12);
                if (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.Span, nC1_);
                    double serr = (1 + _A1m1) * (sig12 + (B12 - _B11)) - s12_a12 / _b;
                    sig12  = sig12 - serr / Sqrt(1 + _k2 * Sq(ssig2_));
                    ssig12 = Sin(sig12); csig12 = Cos(sig12);
                    // Update B12 below
                }
            }

            double ssig2, csig2, sbet2, cbet2, salp2, calp2;

            // sig2 = sig1 + sig12
            ssig2 = _ssig1 * csig12 + _csig1 * ssig12;
            csig2 = _csig1 * csig12 - _ssig1 * ssig12;
            var dn2 = Sqrt(1 + _k2 * Sq(ssig2));

            if (outmask.HasAny(GeodesicFlags.Distance | GeodesicFlags.ReducedLength | GeodesicFlags.GeodesicScale))
            {
                if (arcmode || Abs(_f) > 0.01)
                {
                    B12 = Geodesic.SinCosSeries(true, ssig2, csig2, _C1a.Span, nC1_);
                }
                AB1 = (1 + _A1m1) * (B12 - _B11);
            }
            // sin(bet2) = cos(alp0) * sin(sig2)
            sbet2 = _calp0 * ssig2;
            // Alt: cbet2 = hypot(csig2, salp0 * ssig2);
            cbet2 = Hypot(_salp0, _calp0 * csig2);
            if (cbet2 == 0)
            {
                // I.e., salp0 = 0, csig2 = 0.  Break the degeneracy in this case
                cbet2 = csig2 = tiny_;
            }
            // tan(alp0) = cos(sig2)*tan(alp2)
            salp2 = _salp0; calp2 = _calp0 * csig2; // No need to normalize

            if (outmask.HasAny(GeodesicFlags.Distance))
            {
                s12 = arcmode ? _b * ((1 + _A1m1) * sig12 + AB1) : s12_a12;
            }

            if (outmask.HasAny(GeodesicFlags.Longitude))
            {
                // tan(omg2) = sin(alp0) * tan(sig2)
                double somg2 = _salp0 * ssig2, comg2 = csig2, // No need to normalize
                       E = CopySign(1, _salp0);               // east-going?
                                                              // omg12 = omg2 - omg1
                var omg12 = outmask.HasAny(GeodesicFlags.LongUnroll)
                  ? E * (sig12
                         - (Atan2(ssig2, csig2) - Atan2(_ssig1, _csig1))
                         + (Atan2(E * somg2, comg2) - Atan2(E * _somg1, _comg1)))
                  : Atan2(somg2 * _comg1 - comg2 * _somg1,
                          comg2 * _comg1 + somg2 * _somg1);
                var lam12 = omg12 + _A3c *
                            (sig12 + (Geodesic.SinCosSeries(true, ssig2, csig2, _C3a.Span, nC3_ - 1)
                                      - _B31));
                var lon12 = lam12 / Degree;
                lon2 = outmask.HasAny(GeodesicFlags.LongUnroll) ? _lon1 + lon12 :
                       AngNormalize(AngNormalize(_lon1) + AngNormalize(lon12));
            }

            if (outmask.HasAny(GeodesicFlags.Latitude))
            {
                lat2 = Atan2d(sbet2, _f1 * cbet2);
            }

            if (outmask.HasAny(GeodesicFlags.Azimuth))
            {
                azi2 = Atan2d(salp2, calp2);
            }

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

            if (outmask.HasAny(GeodesicFlags.Area))
            {
                var
                       B42 = Geodesic.SinCosSeries(false, ssig2, csig2, _C4a.Span, nC4_);
                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;
                    // We used to include here some patch up code that purported to deal
                    // with nearly meridional geodesics properly.  However, this turned out
                    // to be wrong once _salp1 = -0 was allowed (via
                    // Geodesic::InverseLine).  In fact, the calculation of {s,c}alp12
                    // was already correct (following the IEEE rules for handling signed
                    // zeros).  So the patch up code was unnecessary (as well as
                    // dangerous).
                }
                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 = Sq(_salp0) + Sq(_calp0) * _csig1 * csig2;
                }
                S12 = _c2 * Atan2(salp12, calp12) + _A4 * (B42 - _B41);
            }

            return(arcmode ? s12_a12 : sig12 / Degree);
        }
Ejemplo n.º 4
0
        private double GenInverse(double lat1, double lon1, double lat2, double lon2,
                                  GeodesicFlags outmask, out double s12,
                                  out double salp1, out double calp1, out double salp2, out double calp2,
                                  out double m12, out double M12, out double M21, out double S12)
        {
            m12   = M12 = M21 = s12 = S12 = double.NaN;
            salp1 = calp1 = salp2 = calp2 = double.NaN;

            // Compute longitude difference (AngDiff does this carefully).  Result is
            // in [-180, 180] but -180 is only for west-going geodesics.  180 is for
            // east-going and meridional geodesics.
            var lon12 = AngDiff(lon1, lon2, out var lon12s);
            // Make longitude difference positive.
            int lonsign = lon12 >= 0 ? 1 : -1;

            // If very close to being on the same half-meridian, then make it so.
            lon12  = lonsign * AngRound(lon12);
            lon12s = AngRound((180 - lon12) - lonsign * lon12s);
            double
                lam12 = lon12 * Degree,
                slam12, clam12;

            if (lon12 > 90)
            {
                SinCosd(lon12s, out slam12, out clam12);
                clam12 = -clam12;
            }
            else
            {
                SinCosd(lon12, out slam12, out clam12);
            }

            // If really close to the equator, treat as on equator.
            lat1 = AngRound(LatFix(lat1));
            lat2 = AngRound(LatFix(lat2));
            // Swap points so that point with higher (abs) latitude is point 1
            // If one latitude is a nan, then it becomes lat1.
            int swapp = Abs(lat1) < Abs(lat2) || double.IsNaN(lat2) ? -1 : 1;

            if (swapp < 0)
            {
                lonsign *= -1;
                Swap(ref lat1, ref lat2);
            }
            // Make lat1 <= 0
            int latsign = lat1 < 0 ? 1 : -1;

            lat1 *= latsign;
            lat2 *= latsign;
            // Now we have
            //
            //     0 <= lon12 <= 180
            //     -90 <= lat1 <= 0
            //     lat1 <= lat2 <= -lat1
            //
            // longsign, swapp, latsign register the transformation to bring the
            // coordinates to this canonical form.  In all cases, 1 means no change was
            // made.  We make these transformations so that there are few cases to
            // check, e.g., on verifying quadrants in atan2.  In addition, this
            // enforces some symmetries in the results returned.

            double s12x = 0, m12x = 0;
            // Initialize for the meridian.  No longitude calculation is done in this
            // case to let the parameter default to 0.
            var E = new EllipticFunction(-_ep2);

            SinCosd(lat1, out var sbet1, out var cbet1); sbet1 *= _f1;
            // Ensure cbet1 = +epsilon at poles; doing the fix on beta means that sig12
            // will be <= 2*tiny for two points at the same pole.
            Norm(ref sbet1, ref cbet1); cbet1 = Max(tiny_, cbet1);

            SinCosd(lat2, out var sbet2, out var cbet2); sbet2 *= _f1;
            // Ensure cbet2 = +epsilon at poles
            Norm(ref sbet2, ref cbet2); cbet2 = Max(tiny_, cbet2);

            // If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the
            // |bet1| - |bet2|.  Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is
            // a better measure.  This logic is used in assigning calp2 in Lambda12.
            // Sometimes these quantities vanish and in that case we force bet2 = +/-
            // bet1 exactly.  An example where is is necessary is the inverse problem
            // 48.522876735459 0 -48.52287673545898293 179.599720456223079643
            // which failed with Visual Studio 10 (Release and Debug)

            if (cbet1 < -sbet1)
            {
                if (cbet2 == cbet1)
                {
                    sbet2 = sbet2 < 0 ? sbet1 : -sbet1;
                }
            }
            else
            {
                if (Abs(sbet2) == -sbet1)
                {
                    cbet2 = cbet1;
                }
            }

            double
                dn1 = (_f >= 0 ? Sqrt(1 + _ep2 * Sq(sbet1)) :
                       Sqrt(1 - _e2 * Sq(cbet1)) / _f1),
                dn2 = (_f >= 0 ? Sqrt(1 + _ep2 * Sq(sbet2)) :
                       Sqrt(1 - _e2 * Sq(cbet2)) / _f1);

            double a12 = double.NaN, sig12;

            bool meridian = lat1 == -90 || slam12 == 0;

            if (meridian)
            {
                // Endpoints are on a single full meridian, so the geodesic might lie on
                // a meridian.

                calp1 = clam12; salp1 = slam12; // Head to the target longitude
                calp2 = 1; salp2 = 0;           // At the target we're heading north

                double
                // tan(bet) = tan(sig) * cos(alp)
                    ssig1 = sbet1, csig1 = calp1 * cbet1,
                    ssig2 = sbet2, csig2 = calp2 * cbet2;

                // sig12 = sig2 - sig1
                sig12 = Atan2(Max(0, csig1 * ssig2 - ssig1 * csig2),
                              csig1 * csig2 + ssig1 * ssig2);
                {
                    Lengths(E, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2,
                            cbet1, cbet2, outmask | GeodesicFlags.ReducedLength,
                            out s12x, out m12x, out _, out M12, out M21);
                }
                // Add the check for sig12 since zero length geodesics might yield m12 <
                // 0.  Test case was
                //
                //    echo 20.001 0 20.001 0 | GeodSolve -i
                //
                // In fact, we will have sig12 > pi/2 for meridional geodesic which is
                // not a shortest path.
                if (sig12 < 1 || m12x >= 0)
                {
                    // Need at least 2, to handle 90 0 90 180
                    if (sig12 < 3 * tiny_ ||
                        // Prevent negative s12 or m12 for short lines
                        (sig12 < tol0_ && (s12x < 0 || m12x < 0)))
                    {
                        sig12 = m12x = s12x = 0;
                    }
                    m12x *= _b;
                    s12x *= _b;
                    a12   = sig12 / Degree;
                }
                else
                {
                    // m12 < 0, i.e., prolate and too close to anti-podal
                    meridian = false;
                }
            }

            // somg12 > 1 marks that it needs to be calculated
            double omg12 = 0, somg12 = 2, comg12 = 0;

            if (!meridian &&
                sbet1 == 0 &&   // and sbet2 == 0
                (_f <= 0 || lon12s >= _f * 180))
            {
                // Geodesic runs along equator
                calp1 = calp2 = 0; salp1 = salp2 = 1;
                s12x  = _a * lam12;
                sig12 = omg12 = lam12 / _f1;
                m12x  = _b * Sin(sig12);
                if (outmask.HasAny(GeodesicFlags.GeodesicScale))
                {
                    M12 = M21 = Cos(sig12);
                }
                a12 = lon12 / _f1;
            }
            else if (!meridian)
            {
                // Now point1 and point2 belong within a hemisphere bounded by a
                // meridian and geodesic is neither meridional or equatorial.

                // Figure a starting point for Newton's method
                sig12 = InverseStart(E, sbet1, cbet1, dn1, sbet2, cbet2, dn2,
                                     lam12, slam12, clam12,
                                     out salp1, out calp1, out salp2, out calp2, out var dnm);

                if (sig12 >= 0)
                {
                    // Short lines (InverseStart sets salp2, calp2, dnm)
                    s12x = sig12 * _b * dnm;
                    m12x = Sq(dnm) * _b * Sin(sig12 / dnm);
                    if (outmask.HasAny(GeodesicFlags.GeodesicScale))
                    {
                        M12 = M21 = Cos(sig12 / dnm);
                    }
                    a12   = sig12 / Degree;
                    omg12 = lam12 / (_f1 * dnm);
                }
                else
                {
                    // Newton's method.  This is a straightforward solution of f(alp1) =
                    // lambda12(alp1) - lam12 = 0 with one wrinkle.  f(alp) has exactly one
                    // root in the interval (0, pi) and its derivative is positive at the
                    // root.  Thus f(alp) is positive for alp > alp1 and negative for alp <
                    // alp1.  During the course of the iteration, a range (alp1a, alp1b) is
                    // maintained which brackets the root and with each evaluation of
                    // f(alp) the range is shrunk, if possible.  Newton's method is
                    // restarted whenever the derivative of f is negative (because the new
                    // value of alp1 is then further from the solution) or if the new
                    // estimate of alp1 lies outside (0,pi); in this case, the new starting
                    // guess is taken to be (alp1a + alp1b) / 2.
                    //
                    // initial values to suppress warnings (if loop is executed 0 times)
                    double ssig1 = 0, csig1 = 0, ssig2 = 0, csig2 = 0, domg12 = 0;
                    uint   numit = 0;
                    // Bracketing range
                    double salp1a = tiny_, calp1a = 1, salp1b = tiny_, calp1b = -1;
                    for (bool tripn = false, tripb = false;
                         numit < maxit2_ || GEOGRAPHICLIB_PANIC;
                         ++numit)
                    {
                        // 1/4 meridian = 10e6 m and random input.  max err is estimated max
                        // error in nm (checking solution of inverse problem by direct
                        // solution).  iter is mean and sd of number of iterations
                        //
                        //           max   iter
                        // log2(b/a) err mean  sd
                        //    -7     387 5.33 3.68
                        //    -6     345 5.19 3.43
                        //    -5     269 5.00 3.05
                        //    -4     210 4.76 2.44
                        //    -3     115 4.55 1.87
                        //    -2      69 4.35 1.38
                        //    -1      36 4.05 1.03
                        //     0      15 0.01 0.13
                        //     1      25 5.10 1.53
                        //     2      96 5.61 2.09
                        //     3     318 6.02 2.74
                        //     4     985 6.24 3.22
                        //     5    2352 6.32 3.44
                        //     6    6008 6.30 3.45
                        //     7   19024 6.19 3.30
                        double dv;
                        var    v = Lambda12(sbet1, cbet1, dn1, sbet2, cbet2, dn2, salp1, calp1,
                                            slam12, clam12,
                                            out salp2, out calp2, out sig12, out ssig1, out csig1, out ssig2, out csig2,
                                            E, out domg12, numit < maxit1_, out dv);
                        // Reversed test to allow escape with NaNs
                        if (tripb || !(Abs(v) >= (tripn ? 8 : 1) * tol0_))
                        {
                            break;
                        }
                        // Update bracketing values
                        if (v > 0 && (numit > maxit1_ || calp1 / salp1 > calp1b / salp1b))
                        {
                            salp1b = salp1; calp1b = calp1;
                        }
                        else if (v < 0 && (numit > maxit1_ || calp1 / salp1 < calp1a / salp1a))
                        {
                            salp1a = salp1; calp1a = calp1;
                        }
                        if (numit < maxit1_ && dv > 0)
                        {
                            var
                                dalp1 = -v / dv;
                            double
                                sdalp1 = Sin(dalp1), cdalp1 = Cos(dalp1),
                                nsalp1 = salp1 * cdalp1 + calp1 * sdalp1;
                            if (nsalp1 > 0 && Abs(dalp1) < PI)
                            {
                                calp1 = calp1 * cdalp1 - salp1 * sdalp1;
                                salp1 = nsalp1;
                                Norm(ref salp1, ref calp1);
                                // In some regimes we don't get quadratic convergence because
                                // slope -> 0.  So use convergence conditions based on epsilon
                                // instead of sqrt(epsilon).
                                tripn = Abs(v) <= 16 * tol0_;
                                continue;
                            }
                        }
                        // Either dv was not positive or updated value was outside legal
                        // range.  Use the midpoint of the bracket as the next estimate.
                        // This mechanism is not needed for the WGS84 ellipsoid, but it does
                        // catch problems with more eccentric ellipsoids.  Its efficacy is
                        // such for the WGS84 test set with the starting guess set to alp1 =
                        // 90deg:
                        // the WGS84 test set: mean = 5.21, sd = 3.93, max = 24
                        // WGS84 and random input: mean = 4.74, sd = 0.99
                        salp1 = (salp1a + salp1b) / 2;
                        calp1 = (calp1a + calp1b) / 2;
                        Norm(ref salp1, ref calp1);
                        tripn = false;
                        tripb = (Abs(salp1a - salp1) + (calp1a - calp1) < tolb_ ||
                                 Abs(salp1 - salp1b) + (calp1 - calp1b) < tolb_);
                    }
                    {
                        Lengths(E, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2,
                                cbet1, cbet2, outmask, out s12x, out m12x, out _, out M12, out M21);
                    }
                    m12x *= _b;
                    s12x *= _b;
                    a12   = sig12 / Degree;
                    if (outmask.HasAny(GeodesicFlags.GeodesicScale))
                    {
                        // omg12 = lam12 - domg12
                        double sdomg12 = Sin(domg12), cdomg12 = Cos(domg12);
                        somg12 = slam12 * cdomg12 - clam12 * sdomg12;
                        comg12 = clam12 * cdomg12 + slam12 * sdomg12;
                    }
                }
            }

            if (outmask.HasAny(GeodesicFlags.Distance))
            {
                s12 = 0 + s12x;           // Convert -0 to 0
            }
            if (outmask.HasAny(GeodesicFlags.ReducedLength))
            {
                m12 = 0 + m12x;           // Convert -0 to 0
            }
            if (outmask.HasAny(GeodesicFlags.Area))
            {
                double
                // From Lambda12: sin(alp1) * cos(bet1) = sin(alp0)
                    salp0 = salp1 * cbet1,
                    calp0 = Hypot(calp1, salp1 * sbet1); // calp0 > 0
                double alp12;
                if (calp0 != 0 && salp0 != 0)
                {
                    double
                    // From Lambda12: tan(bet) = tan(sig) * cos(alp)
                        ssig1 = sbet1, csig1 = calp1 * cbet1,
                        ssig2 = sbet2, csig2 = calp2 * cbet2,
                        k2  = Sq(calp0) * _ep2,
                        eps = k2 / (2 * (1 + Sqrt(1 + k2)) + k2),
                    // Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0).
                        A4 = Sq(_a) * calp0 * salp0 * _e2;
                    Norm(ref ssig1, ref csig1);
                    Norm(ref ssig2, ref csig2);
                    Span <double> C4a = stackalloc double[nC4_];
                    C4f(eps, C4a);
                    double
                        B41 = CosSeries(ssig1, csig1, C4a, nC4_),
                        B42 = CosSeries(ssig2, csig2, C4a, nC4_);
                    S12 = A4 * (B42 - B41);
                }
                else
                {
                    // Avoid problems with indeterminate sig1, sig2 on equator
                    S12 = 0;
                }

                if (!meridian)
                {
                    if (somg12 > 1)
                    {
                        somg12 = Sin(omg12); comg12 = Cos(omg12);
                    }
                }

                if (!meridian &&
                    // omg12 < 3/4 * pi
                    comg12 > -0.7071 && // Long difference not too big
                    sbet2 - sbet1 < 1.75)
                {                       // Lat difference not too big
                  // Use tan(Gamma/2) = tan(omg12/2)
                  // * (tan(bet1/2)+tan(bet2/2))/(1+tan(bet1/2)*tan(bet2/2))
                  // with tan(x/2) = sin(x)/(1+cos(x))
                    double domg12 = 1 + comg12, dbet1 = 1 + cbet1, dbet2 = 1 + cbet2;
                    alp12 = 2 * Atan2(somg12 * (sbet1 * dbet2 + sbet2 * dbet1),
                                      domg12 * (sbet1 * sbet2 + dbet1 * dbet2));
                }
                else
                {
                    // alp12 = alp2 - alp1, used in atan2 so no need to normalize
                    double
                        salp12 = salp2 * calp1 - calp2 * salp1,
                        calp12 = calp2 * calp1 + salp2 * salp1;
                    // The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz
                    // salp12 = -0 and alp12 = -180.  However this depends on the sign
                    // being attached to 0 correctly.  The following ensures the correct
                    // behavior.
                    if (salp12 == 0 && calp12 < 0)
                    {
                        salp12 = tiny_ * calp1;
                        calp12 = -1;
                    }
                    alp12 = Atan2(salp12, calp12);
                }
                S12 += _c2 * alp12;
                S12 *= swapp * lonsign * latsign;
                // Convert -0 to 0
                S12 += 0;
            }

            // Convert calp, salp to azimuth accounting for lonsign, swapp, latsign.
            if (swapp < 0)
            {
                Swap(ref salp1, ref salp2);
                Swap(ref calp1, ref calp2);
                if (outmask.HasAny(GeodesicFlags.GeodesicScale))
                {
                    Swap(ref M12, ref M21);
                }
            }

            salp1 *= swapp * lonsign; calp1 *= swapp * latsign;
            salp2 *= swapp * lonsign; calp2 *= swapp * latsign;

            // Returned value in [0, 180]
            return(a12);
        }