예제 #1
0
        /**
         * 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}.
         **********************************************************************/

        private void SetArc(double a13)
        {
            _a13 = a13;
            GeodesicData g = Position(true, _a13, GeodesicMask.DISTANCE);

            _s13 = g.Distance;
        }
예제 #2
0
        /**
         * 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.ArcLength;
        }
예제 #3
0
        /**
         * Return the results so far.
         * <p>
         * @param reverse if true then clockwise (instead of counter-clockwise)
         *   traversal counts as a positive area.
         * @param sign if true then return a signed result for the area if
         *   the polygon is traversed in the "wrong" direction instead of returning
         *   the area for the rest of the earth.
         * @return PolygonResult(<i>num</i>, <i>perimeter</i>, <i>area</i>) where
         *   <i>num</i> is the number of vertices, <i>perimeter</i> is the perimeter
         *   of the polygon or the length of the polyline (meters), and <i>area</i>
         *   is the area of the polygon (meters<sup>2</sup>) or Double.NaN of
         *   <i>polyline</i> is true in the constructor.
         * <p>
         * More points can be added to the polygon after this call.
         **********************************************************************/

        public PolygonResult Compute(bool reverse, bool sign)
        {
            if (_num < 2)
            {
                return(new PolygonResult(_num, 0, _polyline ? Double.NaN : 0));
            }
            if (_polyline)
            {
                return(new PolygonResult(_num, _perimetersum.Sum(), Double.NaN));
            }

            GeodesicData g       = _earth.Inverse(_lat1, _lon1, _lat0, _lon0, _mask);
            Accumulator  tempsum = new Accumulator(_areasum);

            tempsum.Add(g.GeodesicScale12);
            int crossings = _crossings + Transit(_lon1, _lon0);

            if ((crossings & 1) != 0)
            {
                tempsum.Add((tempsum.Sum() < 0 ? 1 : -1) * _area0 / 2);
            }
            // area is with the clockwise sense.  If !reverse convert to
            // counter-clockwise convention.
            if (!reverse)
            {
                tempsum.Negate();
            }
            // If sign put area in (-area0/2, area0/2], else put area in [0, area0)
            if (sign)
            {
                if (tempsum.Sum() > _area0 / 2)
                {
                    tempsum.Add(-_area0);
                }
                else if (tempsum.Sum() <= -_area0 / 2)
                {
                    tempsum.Add(+_area0);
                }
            }
            else
            {
                if (tempsum.Sum() >= _area0)
                {
                    tempsum.Add(-_area0);
                }
                else if (tempsum.Sum() < 0)
                {
                    tempsum.Add(+_area0);
                }
            }
            return
                (new PolygonResult(_num, _perimetersum.Sum(g.Distance), 0 + tempsum.Sum()));
        }
예제 #4
0
        /**
         * Add an edge to the polygon or polyline.
         * <p>
         * @param azi azimuth at current point (degrees).
         * @param s distance from current point to next point (meters).
         * <p>
         * This does nothing if no points have been added yet.  Use
         * PolygonArea.CurrentPoint to determine the position of the new vertex.
         **********************************************************************/

        public void AddEdge(double azi, double s)
        {
            if (_num > 0)
            {             // Do nothing if _num is zero
                GeodesicData g = _earth.Direct(_lat1, _lon1, azi, s, _mask);
                _perimetersum.Add(g.Distance);
                if (!_polyline)
                {
                    _areasum.Add(g.GeodesicScale12);
                    _crossings += TransitDirect(_lon1, g.Longitude2);
                }
                _lat1 = g.Latitude2; _lon1 = g.Longitude2;
                ++_num;
            }
        }
예제 #5
0
        /**
         * Forward projection, from geographic to gnomonic.
         * <p>
         * @param lat0 latitude of center point of projection (degrees).
         * @param lon0 longitude of center point of projection (degrees).
         * @param lat latitude of point (degrees).
         * @param lon longitude of point (degrees).
         * @return {@link GnomonicData} object with the following fields:
         *   <i>lat0</i>, <i>lon0</i>, <i>lat</i>, <i>lon</i>, <i>x</i>, <i>y</i>,
         *   <i>azi</i>, <i>rk</i>.
         * <p>
         * <i>lat0</i> and <i>lat</i> should be in the range [&minus;90&deg;,
         * 90&deg;] and <i>lon0</i> and <i>lon</i> should be in the range
         * [&minus;540&deg;, 540&deg;). The scale of the projection is
         * 1/<i>rk<sup>2</sup></i> in the "radial" direction, <i>azi</i> clockwise
         * from true north, and is 1/<i>rk</i> in the direction perpendicular to
         * this. If the point lies "over the horizon", i.e., if <i>rk</i> &le; 0,
         * then NaNs are returned for <i>x</i> and <i>y</i> (the correct values are
         * returned for <i>azi</i> and <i>rk</i>). A call to Forward followed by a
         * call to Reverse will return the original (<i>lat</i>, <i>lon</i>) (to
         * within roundoff) provided the point in not over the horizon.
         */

        public GnomonicData Forward(double lat0, double lon0, double lat, double lon)
        {
            GeodesicData inv =
                _earth.Inverse(lat0, lon0, lat, lon,
                               GeodesicMask.AZIMUTH | GeodesicMask.GEODESICSCALE |
                               GeodesicMask.REDUCEDLENGTH);
            GnomonicData fwd =
                new GnomonicData(lat0, lon0, lat, lon, Double.NaN, Double.NaN,
                                 inv.FinalAzimuth, inv.GeodesicScale12);

            if (inv.GeodesicScale12 > 0)
            {
                double rho = inv.ReducedLength / inv.GeodesicScale12;
                Pair   p   = GeoMath.SinCosD(inv.InitialAzimuth);
                fwd.x = rho * p.First;
                fwd.y = rho * p.Second;
            }

            return(fwd);
        }
예제 #6
0
        /**
         * Add a point to the polygon or polyline.
         * <p>
         * @param lat the latitude of the point (degrees).
         * @param lon the latitude of the point (degrees).
         * <p>
         * <i>lat</i> should be in the range [&minus;90&deg;, 90&deg;].
         **********************************************************************/

        public void AddPoint(double lat, double lon)
        {
            lon = GeoMath.AngNormalize(lon);
            if (_num == 0)
            {
                _lat0 = _lat1 = lat;
                _lon0 = _lon1 = lon;
            }
            else
            {
                GeodesicData g = _earth.Inverse(_lat1, _lon1, lat, lon, _mask);
                _perimetersum.Add(g.Distance);
                if (!_polyline)
                {
                    _areasum.Add(g.GeodesicScale12);
                    _crossings += Transit(_lon1, lon);
                }
                _lat1 = lat; _lon1 = lon;
            }
            ++_num;
        }
예제 #7
0
        /**
         * Reverse projection, from gnomonic to geographic.
         * <p>
         * @param lat0 latitude of center point of projection (degrees).
         * @param lon0 longitude of center point of projection (degrees).
         * @param x easting of point (meters).
         * @param y northing of point (meters).
         * @return {@link GnomonicData} object with the following fields:
         *   <i>lat0</i>, <i>lon0</i>, <i>lat</i>, <i>lon</i>, <i>x</i>, <i>y</i>,
         *   <i>azi</i>, <i>rk</i>.
         * <p>
         * <i>lat0</i> should be in the range [&minus;90&deg;, 90&deg;] and
         * <i>lon0</i> should be in the range [&minus;540&deg;, 540&deg;).
         * <i>lat</i> will be in the range [&minus;90&deg;, 90&deg;] and <i>lon</i>
         * will be in the range [&minus;180&deg;, 180&deg;]. The scale of the
         * projection is 1/<i>rk<sup>2</sup></i> in the "radial" direction,
         * <i>azi</i> clockwise from true north, and is 1/<i>rk</i> in the direction
         * perpendicular to this. Even though all inputs should return a valid
         * <i>lat</i> and <i>lon</i>, it's possible that the procedure fails to
         * converge for very large <i>x</i> or <i>y</i>; in this case NaNs are
         * returned for all the output arguments. A call to Reverse followed by a
         * call to Forward will return the original (<i>x</i>, <i>y</i>) (to
         * roundoff).
         */

        public GnomonicData Reverse(double lat0, double lon0, double x, double y)
        {
            GnomonicData rev =
                new GnomonicData(lat0, lon0, Double.NaN, Double.NaN, x, y, Double.NaN,
                                 Double.NaN);

            double azi0   = GeoMath.Atan2d(x, y);
            double rho    = GeoMath.Hypot(x, y);
            double s      = _a * Math.Atan(rho / _a);
            bool   little = rho <= _a;

            if (!little)
            {
                rho = 1 / rho;
            }

            GeodesicLine line =
                _earth.Line(lat0, lon0, azi0, GeodesicMask.LATITUDE
                            | GeodesicMask.LONGITUDE | GeodesicMask.AZIMUTH
                            | GeodesicMask.DISTANCE_IN | GeodesicMask.REDUCEDLENGTH
                            | GeodesicMask.GEODESICSCALE);

            int          count = numit_, trip = 0;
            GeodesicData pos = null;

            while (count-- > 0)
            {
                pos =
                    line.Position(s, GeodesicMask.LONGITUDE | GeodesicMask.LATITUDE
                                  | GeodesicMask.AZIMUTH | GeodesicMask.DISTANCE_IN
                                  | GeodesicMask.REDUCEDLENGTH
                                  | GeodesicMask.GEODESICSCALE);

                if (trip > 0)
                {
                    break;
                }

                double ds =
                    little ? ((pos.ReducedLength / pos.GeodesicScale12) - rho) * pos.GeodesicScale12 * pos.GeodesicScale12
                  : (rho - (pos.GeodesicScale12 / pos.ReducedLength)) * pos.ReducedLength * pos.ReducedLength;
                s -= ds;

                if (Math.Abs(ds) <= eps_ * _a)
                {
                    trip++;
                }
            }

            if (trip == 0)
            {
                return(rev);
            }

            rev.PointLatitude  = pos.Latitude2;
            rev.PointLongitude = pos.Longitude2;
            rev.azi            = pos.FinalAzimuth;
            rev.rk             = pos.GeodesicScale12;

            return(rev);
        }
예제 #8
0
        /**
         * Return the results assuming a tentative final test point is added via an
         * azimuth and distance; however, the data for the test point is not saved.
         * This lets you report a running result for the perimeter and area as the
         * user moves the mouse cursor.  Ordinary floating point arithmetic is used
         * to accumulate the data for the test point; thus the area and perimeter
         * returned are less accurate than if AddPoint and Compute are used.
         * <p>
         * @param azi azimuth at current point (degrees).
         * @param s distance from current point to final test point (meters).
         * @param reverse if true then clockwise (instead of counter-clockwise)
         *   traversal counts as a positive area.
         * @param sign if true then return a signed result for the area if
         *   the polygon is traversed in the "wrong" direction instead of returning
         *   the area for the rest of the earth.
         * @return PolygonResult(<i>num</i>, <i>perimeter</i>, <i>area</i>) where
         *   <i>num</i> is the number of vertices, <i>perimeter</i> is the perimeter
         *   of the polygon or the length of the polyline (meters), and <i>area</i>
         *   is the area of the polygon (meters<sup>2</sup>) or Double.NaN of
         *   <i>polyline</i> is true in the constructor.
         **********************************************************************/

        public PolygonResult TestEdge(double azi, double s,
                                      bool reverse, bool sign)
        {
            if (_num == 0)              // we don't have a starting point!
            {
                return(new PolygonResult(0, Double.NaN, Double.NaN));
            }

            int    num       = _num + 1;
            double perimeter = _perimetersum.Sum() + s;

            if (_polyline)
            {
                return(new PolygonResult(num, perimeter, Double.NaN));
            }

            double tempsum   = _areasum.Sum();
            int    crossings = _crossings;

            {
                double       lat, lon, s12, S12, t;
                GeodesicData g =
                    _earth.Direct(_lat1, _lon1, azi, false, s, _mask);
                tempsum   += g.GeodesicScale12;
                crossings += TransitDirect(_lon1, g.Longitude2);
                g          = _earth.Inverse(g.Latitude2, g.Longitude2, _lat0, _lon0, _mask);
                perimeter += g.Distance;
                tempsum   += g.GeodesicScale12;
                crossings += Transit(g.Longitude2, _lon0);
            }

            if ((crossings & 1) != 0)
            {
                tempsum += (tempsum < 0 ? 1 : -1) * _area0 / 2;
            }
            // area is with the clockwise sense.  If !reverse convert to
            // counter-clockwise convention.
            if (!reverse)
            {
                tempsum *= -1;
            }
            // If sign put area in (-area0/2, area0/2], else put area in [0, area0)
            if (sign)
            {
                if (tempsum > _area0 / 2)
                {
                    tempsum -= _area0;
                }
                else if (tempsum <= -_area0 / 2)
                {
                    tempsum += _area0;
                }
            }
            else
            {
                if (tempsum >= _area0)
                {
                    tempsum -= _area0;
                }
                else if (tempsum < 0)
                {
                    tempsum += _area0;
                }
            }

            return(new PolygonResult(num, perimeter, 0 + tempsum));
        }
예제 #9
0
        /**
         * Return the results assuming a tentative final test point is added;
         * however, the data for the test point is not saved.  This lets you report
         * a running result for the perimeter and area as the user moves the mouse
         * cursor.  Ordinary floating point arithmetic is used to accumulate the
         * data for the test point; thus the area and perimeter returned are less
         * accurate than if AddPoint and Compute are used.
         * <p>
         * @param lat the latitude of the test point (degrees).
         * @param lon the longitude of the test point (degrees).
         * @param reverse if true then clockwise (instead of counter-clockwise)
         *   traversal counts as a positive area.
         * @param sign if true then return a signed result for the area if
         *   the polygon is traversed in the "wrong" direction instead of returning
         *   the area for the rest of the earth.
         * @return PolygonResult(<i>num</i>, <i>perimeter</i>, <i>area</i>) where
         *   <i>num</i> is the number of vertices, <i>perimeter</i> is the perimeter
         *   of the polygon or the length of the polyline (meters), and <i>area</i>
         *   is the area of the polygon (meters<sup>2</sup>) or Double.NaN of
         *   <i>polyline</i> is true in the constructor.
         * <p>
         * <i>lat</i> should be in the range [&minus;90&deg;, 90&deg;].
         **********************************************************************/

        public PolygonResult TestPoint(double lat, double lon,
                                       bool reverse, bool sign)
        {
            if (_num == 0)
            {
                return(new PolygonResult(1, 0, _polyline ? Double.NaN : 0));
            }

            double perimeter = _perimetersum.Sum();
            double tempsum   = _polyline ? 0 : _areasum.Sum();
            int    crossings = _crossings;
            int    num       = _num + 1;

            for (int i = 0; i < (_polyline ? 1 : 2); ++i)
            {
                GeodesicData g =
                    _earth.Inverse(i == 0 ? _lat1 : lat, i == 0 ? _lon1 : lon,
                                   i != 0 ? _lat0 : lat, i != 0 ? _lon0 : lon,
                                   _mask);
                perimeter += g.Distance;
                if (!_polyline)
                {
                    tempsum   += g.GeodesicScale12;
                    crossings += Transit(i == 0 ? _lon1 : lon,
                                         i != 0 ? _lon0 : lon);
                }
            }

            if (_polyline)
            {
                return(new PolygonResult(num, perimeter, Double.NaN));
            }

            if ((crossings & 1) != 0)
            {
                tempsum += (tempsum < 0 ? 1 : -1) * _area0 / 2;
            }
            // area is with the clockwise sense.  If !reverse convert to
            // counter-clockwise convention.
            if (!reverse)
            {
                tempsum *= -1;
            }
            // If sign put area in (-area0/2, area0/2], else put area in [0, area0)
            if (sign)
            {
                if (tempsum > _area0 / 2)
                {
                    tempsum -= _area0;
                }
                else if (tempsum <= -_area0 / 2)
                {
                    tempsum += _area0;
                }
            }
            else
            {
                if (tempsum >= _area0)
                {
                    tempsum -= _area0;
                }
                else if (tempsum < 0)
                {
                    tempsum += _area0;
                }
            }
            return(new PolygonResult(num, perimeter, 0 + tempsum));
        }
예제 #10
0
        /**
         * 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(bool 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.Latitude1  = _lat1; r.InitialAzimuth = _azi1;
            r.Longitude1 = ((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.ArcLength = s12_a12;
                sig12       = Math2.ToRadians(s12_a12);
                {
                    Pair p = GeoMath.SinCosD(s12_a12);
                    ssig12 = p.First; csig12 = p.Second;
                }
            }
            else
            {
                // Interpret s12_a12 as distance
                r.Distance = 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
                        sssig2 = _ssig1 * csig12 + _csig1 * ssig12,
                        scsig2 = _csig1 * csig12 - _ssig1 * ssig12;
                    B12 = Geodesic.SinCosSeries(true, sssig2, scsig2, _C1a);
                    double serr = (1 + _A1m1) * (sig12 + (B12 - _B11)) - s12_a12 / _b;
                    sig12  = sig12 - serr / Math.Sqrt(1 + _k2 * GeoMath.Square(sssig2));
                    ssig12 = Math.Sin(sig12); csig12 = Math.Cos(sig12);
                    // Update B12 below
                }
                r.ArcLength = Math2.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.Square(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 = hypot(csig2, salp0 * ssig2);
            cbet2 = GeoMath.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.Distance = _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 = GeoMath.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 = Math2.ToDegrees(lam12);
                r.Longitude2 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) ? _lon1 + lon12 :
                               GeoMath.AngNormalize(r.Longitude1 + GeoMath.AngNormalize(lon12));
            }

            if ((outmask & GeodesicMask.LATITUDE) != 0)
            {
                r.Latitude2 = GeoMath.Atan2d(sbet2, _f1 * cbet2);
            }

            if ((outmask & GeodesicMask.AZIMUTH) != 0)
            {
                r.FinalAzimuth = 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.ReducedLength = _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.GeodesicScale12 = csig12 + (t * ssig2 - csig2 * J12) * _ssig1 / _dn1;
                    r.GeodesicScale21 = 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.Square(_salp0) + GeoMath.Square(_calp0) * _csig1 * csig2;
                }
                r.AreaUnderGeodesic = _c2 * Math.Atan2(salp12, calp12) + _A4 * (B42 - _B41);
            }

            return(r);
        }