Beispiel #1
0
        // The static const coefficient arrays in the following functions are
        // generated by Maxima and give the coefficients of the Taylor expansions for
        // the geodesics.  The convention on the order of these coefficients is as
        // follows:
        //
        //   ascending order in the trigonometric expansion,
        //   then powers of eps in descending order,
        //   finally powers of n in descending order.
        //
        // (For some expansions, only a subset of levels occur.)  For each polynomial
        // of order n at the lowest level, the (n+1) coefficients of the polynomial
        // are followed by a divisor which is applied to the whole polynomial.  In
        // this way, the coefficients are expressible with no round off error.  The
        // sizes of the coefficient arrays are:
        //
        //   A1m1f, A2m1f            = floor(N/2) + 2
        //   C1f, C1pf, C2f, A3coeff = (N^2 + 7*N - 2*floor(N/2)) / 4
        //   C3coeff       = (N - 1) * (N^2 + 7*N - 2*floor(N/2)) / 8
        //   C4coeff       = N * (N + 1) * (N + 5) / 6
        //
        // where N = GEOGRAPHICLIB_GEODESIC_ORDER
        //         = nA1 = nA2 = nC1 = nC1p = nA3 = nC4

        // The scale factor A1-1 = mean value of (d/dsigma)I1 - 1
        public static double A1m1f(double eps)
        {
            double[] coeff =
            {
                // (1-eps)*A1-1, polynomial in eps2 of order 3
                1, 4, 64, 0, 256,
            };
            int    m = nA1_ / 2;
            double t = GeoMath.PolyVal(m, coeff, 0, GeoMath.Square(eps)) / coeff[m + 1];

            return((t + eps) / (1 - eps));
        }
Beispiel #2
0
        /**
         * Set the central point of the projection
         *
         * @param[in] lat0 latitude of center point of projection (degrees).
         * @param[in] lon0 longitude of center point of projection (degrees).
         *
         * \e lat0 should be in the range [−90°, 90°].
         **********************************************************************/
        public void Reset(double lat0, double lon0)
        {
            _meridian = _earth.Line(lat0, lon0, 0,
                                    GeodesicMask.LATITUDE | GeodesicMask.LONGITUDE |
                                    GeodesicMask.DISTANCE | GeodesicMask.DISTANCE_IN |
                                    GeodesicMask.AZIMUTH);
            double f = _earth.Flattening();

            GeoMath.Sincosd(LatitudeOrigin(), out _sbet0, out _cbet0);
            _sbet0 *= (1 - f);
            GeoMath.Norm(ref _sbet0, ref _cbet0);
        }
Beispiel #3
0
        /**
         * Reset the origin.
         *
         * @param[in] lat0 latitude at origin (degrees).
         * @param[in] lon0 longitude at origin (degrees).
         * @param[in] h0 height above ellipsoid at origin (meters); default 0.
         *
         * \e lat0 should be in the range [−90°, 90°].
         **********************************************************************/
        public void Reset(double lat0, double lon0, double h0 = 0)
        {
            _lat0 = GeoMath.LatFix(lat0);
            _lon0 = GeoMath.AngNormalize(lon0);
            _h0   = h0;
            _earth.Forward(_lat0, _lon0, _h0, out _x0, out _y0, out _z0);
            double sphi, cphi, slam, clam;

            GeoMath.Sincosd(_lat0, out sphi, out cphi);
            GeoMath.Sincosd(_lon0, out slam, out clam);
            Geocentric.Rotation(sphi, cphi, slam, clam, _r);
        }
Beispiel #4
0
        // The scale factor A2-1 = mean value of (d/dsigma)I2 - 1
        public static double A2m1f(double eps)
        {
            double[] coeff =
            {
                // (eps+1)*A2-1, polynomial in eps2 of order 3
                -11, -28, -192, 0, 256,
            };              // count = 5

            int    m = nA2_ / 2;
            double t = GeoMath.PolyVal(m, coeff, 0, GeoMath.Square(eps)) / coeff[m + 1];

            return((t - eps) / (1 + eps));
        }
 void InitVars(double a, double f)
 {
     eps_   = GeoMath.Epsilon;
     epsx_  = GeoMath.Square(eps_);
     epsx2_ = GeoMath.Square(epsx_);
     tol_   = GeoMath.Square(eps_);
     tol0_  = (tol_ * GeoMath.Square(GeoMath.Square(eps_)));
     _a     = a;
     _f     = f;
     _fm    = 1 - _f;
     _e2    = _f * (2 - _f);
     _e     = GeoMath.Square(Math.Abs(_e2));
     _e2m   = 1 - _e2;
     _qZ    = 1 + _e2m * atanhee(1);
     _qx    = _qZ / (2 * _e2m);
 }
        /**
         * Set the azimuthal scale for the projection.
         *
         * @param[in] lat (degrees).
         * @param[in] k azimuthal scale at latitude \e lat (default 1).
         * @exception new GeographicException \e k is not positive.
         * @exception new GeographicException if \e lat is not in (−90°,
         *   90°).
         *
         * This allows a "latitude of conformality" to be specified.
         **********************************************************************/
        public void SetScale(double lat, double k = 1)
        {
            if (!(GeoMath.IsFinite(k) && k > 0))
            {
                throw new GeographicException("Scale is not positive");
            }
            if (!(Math.Abs(lat) < 90))
            {
                throw new GeographicException("Latitude for SetScale not in (-90d, 90d)");
            }
            double x, y, gamma, kold;

            Forward(0, lat, 0, out x, out y, out gamma, out kold);
            k   /= kold;
            _k0 *= k;
            _k2  = GeoMath.Square(_k0);
        }
Beispiel #7
0
        internal void IntForward(double lat, double lon, double h, out double X, out double Y, out double Z,
                                 double[] M)
        {
            double sphi, cphi, slam, clam;

            GeoMath.Sincosd(GeoMath.LatFix(lat), out sphi, out cphi);
            GeoMath.Sincosd(lon, out slam, out clam);
            double n = _a / Math.Sqrt(1 - _e2 * GeoMath.Square(sphi));

            Z  = (_e2m * n + h) * sphi;
            X  = (n + h) * cphi;
            Y  = X * slam;
            X *= clam;
            if (M != null)
            {
                Rotation(sphi, cphi, slam, clam, M);
            }
        }
        /**
         * ructor with two standard parallels.
         *
         * @param[in] a equatorial radius of ellipsoid (meters).
         * @param[in] f flattening of ellipsoid.  Setting \e f = 0 gives a sphere.
         *   Negative \e f gives a prolate ellipsoid.
         * @param[in] stdlat1 first standard parallel (degrees).
         * @param[in] stdlat2 second standard parallel (degrees).
         * @param[in] k1 azimuthal scale on the standard parallels.
         * @exception new GeographicException if \e a, (1 &minus; \e f) \e a, or \e k1 is
         *   not positive.
         * @exception new GeographicException if \e stdlat1 or \e stdlat2 is not in
         *   [&minus;90&deg;, 90&deg;], or if \e stdlat1 and \e stdlat2 are
         *   opposite poles.
         **********************************************************************/
        public AlbersEqualArea(double a, double f, double stdlat1, double stdlat2, double k1)
        {
            InitVars(a, f);

            eps_   = GeoMath.Epsilon;
            epsx_  = GeoMath.Square(eps_);
            epsx2_ = GeoMath.Square(epsx_);
            tol_   = GeoMath.Square(eps_);
            tol0_  = (tol_ * GeoMath.Square(GeoMath.Square(eps_)));
            _a     = a;
            _f     = f;
            _fm    = 1 - _f;
            _e2    = _f * (2 - _f);
            _e     = GeoMath.Square(Math.Abs(_e2));
            _e2m   = 1 - _e2;
            _qZ    = 1 + _e2m * atanhee(1);
            _qx    = _qZ / (2 * _e2m);

            if (!(GeoMath.IsFinite(_a) && _a > 0))
            {
                throw new GeographicException("Equatorial radius is not positive");
            }
            if (!(GeoMath.IsFinite(_f) && _f < 1))
            {
                throw new GeographicException("Polar semi-axis is not positive");
            }
            if (!(GeoMath.IsFinite(k1) && k1 > 0))
            {
                throw new GeographicException("Scale is not positive");
            }
            if (!(Math.Abs(stdlat1) <= 90))
            {
                throw new GeographicException("Standard latitude 1 not in [-90d, 90d]");
            }
            if (!(Math.Abs(stdlat2) <= 90))
            {
                throw new GeographicException("Standard latitude 2 not in [-90d, 90d]");
            }
            double sphi1, cphi1, sphi2, cphi2;

            GeoMath.Sincosd(stdlat1, out sphi1, out cphi1);
            GeoMath.Sincosd(stdlat2, out sphi2, out cphi2);
            Init(sphi1, cphi1, sphi2, cphi2, k1);
        }
Beispiel #9
0
        public void Add(double y)
        {
            // Here's Shewchuk's solution...
            double u;                       // hold exact sum as [s, t, u]

            // Accumulate starting at least significant end
            y  = GeoMath.Sum(y, _t, out u);
            _s = GeoMath.Sum(y, _s, out _t);
            // Start is _s, _t decreasing and non-adjacent.  Sum is now (s + t + u)
            // exactly with s, t, u non-adjacent and in decreasing order (except for
            // possible zeros).  The following code tries to normalize the result.
            // Ideally, we want _s = round(s+t+u) and _u = round(s+t+u - _s).  The
            // following does an approximate job (and maintains the decreasing
            // non-adjacent property).  Here are two "failures" using 3-bit floats:
            //
            // Case 1: _s is not equal to round(s+t+u) -- off by 1 ulp
            // [12, -1] - 8 -> [4, 0, -1] -> [4, -1] = 3 should be [3, 0] = 3
            //
            // Case 2: _s+_t is not as close to s+t+u as it shold be
            // [64, 5] + 4 -> [64, 8, 1] -> [64,  8] = 72 (off by 1)
            //                    should be [80, -7] = 73 (exact)
            //
            // "Fixing" these problems is probably not worth the expense.  The
            // representation inevitably leads to small errors in the accumulated
            // values.  The additional errors illustrated here amount to 1 ulp of the
            // less significant word during each addition to the Accumulator and an
            // additional possible error of 1 ulp in the reported sum.
            //
            // Incidentally, the "ideal" representation described above is not
            // canonical, because _s = round(_s + _t) may not be true.  For example,
            // with 3-bit floats:
            //
            // [128, 16] + 1 -> [160, -16] -- 160 = round(145).
            // But [160, 0] - 16 -> [128, 16] -- 128 = round(144).
            //
            if (_s == 0)              // This implies t == 0,
            {
                _s = u;               // so result is u
            }
            else
            {
                _t += u;                // otherwise just accumulate u to t.
            }
        }
Beispiel #10
0
        /**
         * Constructor for a ellipsoid with
         *
         * @param[in] a equatorial radius (meters).
         * @param[in] f flattening of ellipsoid.  Setting \e f = 0 gives a sphere.
         *   Negative \e f gives a prolate ellipsoid.
         * @exception GeographicErr if \e a or (1 &minus; \e f) \e a is not
         *   positive.
         **********************************************************************/
        public Geocentric(double a, double f)
        {
            _a      = a;
            _f      = f;
            _e2     = _f * (2 - _f);
            _e2m    = GeoMath.Square(1 - _f);
            _e2a    = Math.Abs(_e2);
            _e4a    = GeoMath.Square(_e2);
            _maxrad = 2 * _a / GeoMath.Epsilon;

            if (!(GeoMath.IsFinite(_a) && _a > 0))
            {
                throw new GeographicException("Equatorial radius is not positive");
            }
            if (!(GeoMath.IsFinite(_f) && _f < 1))
            {
                throw new GeographicException("Polar semi-axis is not positive");
            }
        }
        /**
         * ructor with two standard parallels specified by sines and cosines.
         *
         * @param[in] a equatorial radius of ellipsoid (meters).
         * @param[in] f flattening of ellipsoid.  Setting \e f = 0 gives a sphere.
         *   Negative \e f gives a prolate ellipsoid.
         * @param[in] sinlat1 sine of first standard parallel.
         * @param[in] coslat1 cosine of first standard parallel.
         * @param[in] sinlat2 sine of second standard parallel.
         * @param[in] coslat2 cosine of second standard parallel.
         * @param[in] k1 azimuthal scale on the standard parallels.
         * @exception new GeographicException if \e a, (1 &minus; \e f) \e a, or \e k1 is
         *   not positive.
         * @exception new GeographicException if \e stdlat1 or \e stdlat2 is not in
         *   [&minus;90&deg;, 90&deg;], or if \e stdlat1 and \e stdlat2 are
         *   opposite poles.
         *
         * This allows parallels close to the poles to be specified accurately.
         * This routine computes the latitude of origin and the azimuthal scale at
         * this latitude.  If \e dlat = abs(\e lat2 &minus; \e lat1) &le; 160&deg;,
         * then the error in the latitude of origin is less than 4.5 &times;
         * 10<sup>&minus;14</sup>d;.
         **********************************************************************/
        public AlbersEqualArea(double a, double f,
                               double sinlat1, double coslat1,
                               double sinlat2, double coslat2,
                               double k1)
        {
            InitVars(a, f);

            if (!(GeoMath.IsFinite(_a) && _a > 0))
            {
                throw new GeographicException("Equatorial radius is not positive");
            }
            if (!(GeoMath.IsFinite(_f) && _f < 1))
            {
                throw new GeographicException("Polar semi-axis is not positive");
            }
            if (!(GeoMath.IsFinite(k1) && k1 > 0))
            {
                throw new GeographicException("Scale is not positive");
            }
            if (!(coslat1 >= 0))
            {
                throw new GeographicException("Standard latitude 1 not in [-90d, 90d]");
            }
            if (!(coslat2 >= 0))
            {
                throw new GeographicException("Standard latitude 2 not in [-90d, 90d]");
            }
            if (!(Math.Abs(sinlat1) <= 1 && coslat1 <= 1) || (coslat1 == 0 && sinlat1 == 0))
            {
                throw new GeographicException("Bad sine/cosine of standard latitude 1");
            }
            if (!(Math.Abs(sinlat2) <= 1 && coslat2 <= 1) || (coslat2 == 0 && sinlat2 == 0))
            {
                throw new GeographicException("Bad sine/cosine of standard latitude 2");
            }
            if (coslat1 == 0 && coslat2 == 0 && sinlat1 * sinlat2 <= 0)
            {
                throw new GeographicException
                          ("Standard latitudes cannot be opposite poles");
            }
            Init(sinlat1, coslat1, sinlat2, coslat2, k1);
        }
Beispiel #12
0
        public static double[] GenerateC4(double n)
        {
            double[] coeff =
            {
                97,      15015,                              // C4[0], coeff of eps^5, polynomial in n of order 0
                1088,      156, 45045,                       // C4[0], coeff of eps^4, polynomial in n of order 1
                -224,    -4784,  1573, 45045,                // C4[0], coeff of eps^3, polynomial in n of order 2
                -10656,  14144, -4576,  -858,  45045,        // C4[0], coeff of eps^2, polynomial in n of order 3
                64,        624, -4576,  6864,  -3003,15015,                             // C4[0], coeff of eps^1, polynomial in n of order 4
                100,       208,   572,  3432, -12012,30030, 45045,                      // C4[0], coeff of eps^0, polynomial in n of order 5
                1,        9009,                              // C4[1], coeff of eps^5, polynomial in n of order 0
                -2944,     468,135135,                       // C4[1], coeff of eps^4, polynomial in n of order 1
                5792,     1040, -1287,135135,                // C4[1], coeff of eps^3, polynomial in n of order 2
                5952,   -11648,  9152, -2574, 135135,        // C4[1], coeff of eps^2, polynomial in n of order 3
                -64,      -624,  4576, -6864,   3003,135135,                            // C4[1], coeff of eps^1, polynomial in n of order 4
                8,       10725,                              // C4[2], coeff of eps^5, polynomial in n of order 0
                1856,     -936,225225,                       // C4[2], coeff of eps^4, polynomial in n of order 1
                -8448,    4992, -1144,225225,                // C4[2], coeff of eps^3, polynomial in n of order 2
                -1440,    4160, -4576,  1716, 225225,        // C4[2], coeff of eps^2, polynomial in n of order 3
                -136,    63063,                              // C4[3], coeff of eps^5, polynomial in n of order 0
                1024,     -208,105105,                       // C4[3], coeff of eps^4, polynomial in n of order 1
                3584,    -3328,  1144,315315,                // C4[3], coeff of eps^3, polynomial in n of order 2
                -128,   135135,                              // C4[4], coeff of eps^5, polynomial in n of order 0
                -2560,     832,405405,                       // C4[4], coeff of eps^4, polynomial in n of order 1
                128, 99099                                   // C4[5], coeff of eps^5, polynomial in n of order 0
            };

            var C4x = new double[nC4x_];

            int o = 0, k = 0;

            for (int l = 0; l < nC4_; ++l)          // l is index of C4[l]
            {
                for (int j = nC4_ - 1; j >= l; --j) // coeff of eps^j
                {
                    int m = nC4_ - j - 1;           // order of polynomial in n
                    C4x[k++] = GeoMath.PolyVal(m, coeff, o, n) / coeff[o + m + 1];
                    o       += m + 2;
                }
            }
            return(C4x);
        }
        /**
         * Reverse projection, from Lambert conformal conic to geographic.
         *
         * @param[in] lon0 central meridian longitude (degrees).
         * @param[in] x easting of point (meters).
         * @param[in] y northing of point (meters).
         * @param[out] lat latitude of point (degrees).
         * @param[out] lon longitude of point (degrees).
         * @param[out] gamma meridian convergence at point (degrees).
         * @param[out] k azimuthal scale of projection at point; the radial
         *   scale is the 1/\e k.
         *
         * The latitude origin is given by AlbersEqualArea::LatitudeOrigin().  No
         * false easting or northing is added.  The value of \e lon returned is in
         * the range [&minus;180&deg;, 180&deg;].  The value of \e lat returned is
         * in the range [&minus;90&deg;, 90&deg;].  If the input point is outside
         * the legal projected space the nearest pole is returned.
         **********************************************************************/
        public void Reverse(double lon0, double x, double y,
                            out double lat, out double lon, out double gamma, out double k)
        {
            y *= _sign;
            double
                nx = _k0 * _n0 * x, ny = _k0 * _n0 * y, y1 = _nrho0 - ny,
                den  = GeoMath.Hypot(nx, y1) + _nrho0, // 0 implies origin with polar aspect
                drho = den != 0 ? (_k0 * x * nx - 2 * _k0 * y * _nrho0 + _k0 * y * ny) / den : 0,
            // dsxia = scxi0 * dsxi
                dsxia = -_scxi0 * (2 * _nrho0 + _n0 * drho) * drho /
                        (GeoMath.Square(_a) * _qZ),
                txi   = (_txi0 + dsxia) / Math.Sqrt(Math.Max(1 - dsxia * (2 * _txi0 + dsxia), epsx2_)),
                tphi  = tphif(txi),
                theta = Math.Atan2(nx, y1),
                lam   = _n0 != 0 ? theta / (_k2 * _n0) : x / (y1 * _k0);

            gamma = _sign * theta / GeoMath.Degree;
            lat   = GeoMath.Atand(_sign * tphi);
            lon   = lam / GeoMath.Degree;
            lon   = GeoMath.AngNormalize(lon + GeoMath.AngNormalize(lon0));
            k     = _k0 * (den != 0 ? (_nrho0 + _n0 * drho) * hyp(_fm * tphi) / _a : 1);
        }
        // return atanh(GeoMath.Square(x))/GeoMath.Square(x) - 1, accurate for small x
        static double atanhxm1(double x)
        {
            double s = 0;

            if (Math.Abs(x) < 0.5)
            {
                double os = -1, y = 1, k = 1;
                while (os != s)
                {
                    os = s;
                    y *= x;                 // y = x^n
                    k += 2;                 // k = 2*n + 1
                    s += y / k;             // sum( x^n/(2*n + 1) )
                }
            }
            else
            {
                double xs = Math.Sqrt(Math.Abs(x));
                s = (x > 0 ? GeoMath.Atanh(xs) : Math.Atan(xs)) / xs - 1;
            }
            return(s);
        }
Beispiel #15
0
        //TODO: Can make this more efficient by cloning the array once it has been created

        public static double[] GenerateA3(double n)
        {
            double[] coeff =
            {
                -3,128,         // coeff of eps^5, polynomial in n of order 0
                -2, -3, 64,     // coeff of eps^4, polynomial in n of order 1
                -1, -3, -1, 16, // coeff of eps^3, polynomial in n of order 2
                3,  -1, -2, 8,  // coeff of eps^2, polynomial in n of order 2
                1,  -1,  2,     // coeff of eps^1, polynomial in n of order 1
                1,   1,         // coeff of eps^0, polynomial in n of order 0
            };

            var A3x = new double[nA3x_];

            int o = 0, k = 0;

            for (int j = nA3_ - 1; j >= 0; --j)    // coeff of eps^j
            {
                int m = Math.Min(nA3_ - j - 1, j); // order of polynomial in n
                A3x[k++] = GeoMath.PolyVal(m, coeff, o, n) / coeff[o + m + 1];
                o       += m + 2;
            }
            return(A3x);
        }
Beispiel #16
0
        public static double[] GenerateC3(double n)
        {
            double[] coeff =
            {
                3,  128,         // C3[1], coeff of eps^5, polynomial in n of order 0
                2,    5,128,                         // C3[1], coeff of eps^4, polynomial in n of order 1
                -1,   3, 3, 64,  // C3[1], coeff of eps^3, polynomial in n of order 2
                -1,   0, 1, 8,   // C3[1], coeff of eps^2, polynomial in n of order 2
                -1,   1, 4,      // C3[1], coeff of eps^1, polynomial in n of order 1
                5,  256,         // C3[2], coeff of eps^5, polynomial in n of order 0
                1,    3,128,                         // C3[2], coeff of eps^4, polynomial in n of order 1
                -3,  -2, 3, 64,  // C3[2], coeff of eps^3, polynomial in n of order 2
                1,   -3, 2, 32,  // C3[2], coeff of eps^2, polynomial in n of order 2
                7,  512,         // C3[3], coeff of eps^5, polynomial in n of order 0
                -10,  9,384,                         // C3[3], coeff of eps^4, polynomial in n of order 1
                5,   -9, 5, 192, // C3[3], coeff of eps^3, polynomial in n of order 2
                7,  512,         // C3[4], coeff of eps^5, polynomial in n of order 0
                -14,  7,512,                         // C3[4], coeff of eps^4, polynomial in n of order 1
                21, 2560         // C3[5], coeff of eps^5, polynomial in n of order 0
            };

            var C3x = new double[nC3x_];

            int o = 0, k = 0;

            for (int l = 1; l < nC3_; ++l)             // l is index of C3[l]
            {
                for (int j = nC3_ - 1; j >= l; --j)    // coeff of eps^j
                {
                    int m = Math.Min(nC3_ - j - 1, j); // order of polynomial in n
                    C3x[k++] = GeoMath.PolyVal(m, coeff, o, n) / coeff[o + m + 1];
                    o       += m + 2;
                }
            }
            return(C3x);
        }
        /**
         * Forward projection, from geographic to transverse Mercator.
         *
         * @param[in] lon0 central meridian of the projection (degrees).
         * @param[in] lat latitude of point (degrees).
         * @param[in] lon longitude of point (degrees).
         * @param[out] x easting of point (meters).
         * @param[out] y northing of point (meters).
         * @param[out] gamma meridian convergence at point (degrees).
         * @param[out] k scale of projection at point.
         *
         * No false easting or northing is added. \e lat should be in the range
         * [&minus;90&deg;, 90&deg;].
         **********************************************************************/
        public void Forward(double lon0, double lat, double lon,
                            out double x, out double y, out double gamma, out double k)
        {
            double e;

            lat = GeoMath.LatFix(lat);
            lon = GeoMath.AngDiff(lon0, lon, out e);
            // Explicitly enforce the parity
            int
                latsign = (lat < 0) ? -1 : 1,
                lonsign = (lon < 0) ? -1 : 1;

            lon *= lonsign;
            lat *= latsign;
            bool backside = lon > 90;

            if (backside)
            {
                if (lat == 0)
                {
                    latsign = -1;
                }
                lon = 180 - lon;
            }
            double sphi, cphi, slam, clam;

            GeoMath.Sincosd(lat, out sphi, out cphi);
            GeoMath.Sincosd(lon, out slam, out clam);
            // phi = latitude
            // phi' = conformal latitude
            // psi = isometric latitude
            // tau = tan(phi)
            // tau' = tan(phi')
            // [xi', eta'] = Gauss-Schreiber TM coordinates
            // [xi, eta] = Gauss-Krueger TM coordinates
            //
            // We use
            //   tan(phi') = Math.Sinh(psi)
            //   Math.Sin(phi') = tanh(psi)
            //   Math.Cos(phi') = sech(psi)
            //   denom^2    = 1-Math.Cos(phi')^2*Math.Sin(lam)^2 = 1-sech(psi)^2*Math.Sin(lam)^2
            //   Math.Sin(xip)   = Math.Sin(phi')/denom          = tanh(psi)/denom
            //   Math.Cos(xip)   = Math.Cos(phi')*Math.Cos(lam)/denom = sech(psi)*Math.Cos(lam)/denom
            //   Math.Cosh(etap) = 1/denom                  = 1/denom
            //   Math.Sinh(etap) = Math.Cos(phi')*Math.Sin(lam)/denom = sech(psi)*Math.Sin(lam)/denom
            double etap, xip;

            if (lat != 90)
            {
                double
                    tau  = sphi / cphi,
                    taup = GeoMath.Taupf(tau, _es);
                xip = Math.Atan2(taup, clam);
                // Used to be
                //   etap = Math::atanh(Math.Sin(lam) / Math.Cosh(psi));
                etap = GeoMath.Asinh(slam / GeoMath.Hypot(taup, clam));
                // convergence and scale for Gauss-Schreiber TM (xip, etap) -- gamma0 =
                // atan(tan(xip) * tanh(etap)) = atan(tan(lam) * Math.Sin(phi'));
                // Math.Sin(phi') = tau'/Math.Sqrt(1 + tau'^2)
                // Krueger p 22 (44)
                gamma = GeoMath.Atan2d(slam * taup, clam * GeoMath.Hypot(1, taup));
                // k0 = Math.Sqrt(1 - _e2 * Math.Sin(phi)^2) * (Math.Cos(phi') / Math.Cos(phi)) * Math.Cosh(etap)
                // Note 1/Math.Cos(phi) = Math.Cosh(psip);
                // and Math.Cos(phi') * Math.Cosh(etap) = 1/hypot(Math.Sinh(psi), Math.Cos(lam))
                //
                // This form has cancelling errors.  This property is lost if Math.Cosh(psip)
                // is replaced by 1/Math.Cos(phi), even though it's using "primary" data (phi
                // instead of psip).
                k = Math.Sqrt(_e2m + _e2 * GeoMath.Square(cphi)) * GeoMath.Hypot(1, tau)
                    / GeoMath.Hypot(taup, clam);
            }
            else
            {
                xip   = Math.PI / 2;
                etap  = 0;
                gamma = lon;
                k     = _c;
            }
            // {xi',eta'} is {northing,easting} for Gauss-Schreiber transverse Mercator
            // (for eta' = 0, xi' = bet). {xi,eta} is {northing,easting} for transverse
            // Mercator with ant scale on the central meridian (for eta = 0, xip =
            // rectifying latitude).  Define
            //
            //   zeta = xi + i*eta
            //   zeta' = xi' + i*eta'
            //
            // The conversion from conformal to rectifying latitude can be expressed as
            // a series in _n:
            //
            //   zeta = zeta' + sum(h[j-1]' * Math.Sin(2 * j * zeta'), j = 1..maxpow_)
            //
            // where h[j]' = O(_n^j).  The reversion of this series gives
            //
            //   zeta' = zeta - sum(h[j-1] * Math.Sin(2 * j * zeta), j = 1..maxpow_)
            //
            // which is used in Reverse.
            //
            // Evaluate sums via Clenshaw method.  See
            //    https://en.wikipedia.org/wiki/Clenshaw_algorithm
            //
            // Let
            //
            //    S = sum(a[k] * phi[k](x), k = 0..n)
            //    phi[k+1](x) = alpha[k](x) * phi[k](x) + beta[k](x) * phi[k-1](x)
            //
            // Evaluate S with
            //
            //    b[n+2] = b[n+1] = 0
            //    b[k] = alpha[k](x) * b[k+1] + beta[k+1](x) * b[k+2] + a[k]
            //    S = (a[0] + beta[1](x) * b[2]) * phi[0](x) + b[1] * phi[1](x)
            //
            // Here we have
            //
            //    x = 2 * zeta'
            //    phi[k](x) = Math.Sin(k * x)
            //    alpha[k](x) = 2 * Math.Cos(x)
            //    beta[k](x) = -1
            //    [ Math.Sin(A+B) - 2*Math.Cos(B)*Math.Sin(A) + Math.Sin(A-B) = 0, A = k*x, B = x ]
            //    n = maxpow_
            //    a[k] = _alp[k]
            //    S = b[1] * Math.Sin(x)
            //
            // For the derivative we have
            //
            //    x = 2 * zeta'
            //    phi[k](x) = Math.Cos(k * x)
            //    alpha[k](x) = 2 * Math.Cos(x)
            //    beta[k](x) = -1
            //    [ Math.Cos(A+B) - 2*Math.Cos(B)*Math.Cos(A) + Math.Cos(A-B) = 0, A = k*x, B = x ]
            //    a[0] = 1; a[k] = 2*k*_alp[k]
            //    S = (a[0] - b[2]) + b[1] * Math.Cos(x)
            //
            // Matrix formulation (not used here):
            //    phi[k](x) = [Math.Sin(k * x); k * Math.Cos(k * x)]
            //    alpha[k](x) = 2 * [Math.Cos(x), 0; -Math.Sin(x), Math.Cos(x)]
            //    beta[k](x) = -1 * [1, 0; 0, 1]
            //    a[k] = _alp[k] * [1, 0; 0, 1]
            //    b[n+2] = b[n+1] = [0, 0; 0, 0]
            //    b[k] = alpha[k](x) * b[k+1] + beta[k+1](x) * b[k+2] + a[k]
            //    N.B., for all k: b[k](1,2) = 0; b[k](1,1) = b[k](2,2)
            //    S = (a[0] + beta[1](x) * b[2]) * phi[0](x) + b[1] * phi[1](x)
            //    phi[0](x) = [0; 0]
            //    phi[1](x) = [Math.Sin(x); Math.Cos(x)]
            double
                c0 = Math.Cos(2 * xip), ch0 = Math.Cosh(2 * etap),
                s0 = Math.Sin(2 * xip), sh0 = Math.Sinh(2 * etap);

            int     n  = maxpow_;
            Complex a  = new Complex(2 * c0 * ch0, -2 * s0 * sh0); // 2 * Math.Cos(2*zeta')
            Complex y0 = new Complex((n & 1) != 0 ? _alp[n] : 0, 0);
            Complex y1;                                            // default initializer is 0+i0
            Complex z0 = new Complex((n & 1) != 0 ? 2 * n * _alp[n] : 0, 0);
            Complex z1;

            if ((n & 1) != 0)
            {
                --n;
            }
            while (n > 0)
            {
                y1 = a * y0 - y1 + _alp[n];
                z1 = a * z0 - z1 + 2 * n * _alp[n];
                --n;
                y0 = a * y1 - y0 + _alp[n];
                z0 = a * z1 - z0 + 2 * n * _alp[n];
                --n;
            }
            a /= 2;                               // Math.Cos(2*zeta')
            z1 = 1 - z1 + a * z0;
            a  = new Complex(s0 * ch0, c0 * sh0); // Math.Sin(2*zeta')
            y1 = new Complex(xip, etap) + a * y0;
            // Fold in change in convergence and scale for Gauss-Schreiber TM to
            // Gauss-Krueger TM.
            gamma -= GeoMath.Atan2d(z1.Imaginary, z1.Real);
            k     *= _b1 * Complex.Abs(z1);
            double xi = y1.Real, eta = y1.Imaginary;

            y = _a1 * _k0 * (backside ? Math.PI - xi : xi) * latsign;
            x = _a1 * _k0 * eta * lonsign;
            if (backside)
            {
                gamma = 180 - gamma;
            }
            gamma *= latsign * lonsign;
            gamma  = GeoMath.AngNormalize(gamma);
            k     *= _k0;
        }
Beispiel #18
0
        internal void IntReverse(double X, double Y, double Z, out double lat, out double lon, out double h,
                                 double[] M)
        {
            double
                R    = GeoMath.Hypot(X, Y),
                slam = R != 0 ? Y / R : 0,
                clam = R != 0 ? X / R : 1;

            h = GeoMath.Hypot(R, Z);      // Distance to center of earth
            double sphi, cphi;

            if (h > _maxrad)
            {
                // We doublely far away (> 12 million light years); treat the earth as a
                // point and h, above, is an acceptable approximation to the height.
                // This avoids overflow, e.g., in the computation of disc below.  It's
                // possible that h has overflowed to inf; but that's OK.
                //
                // Treat the case X, Y finite, but R overflows to +inf by scaling by 2.
                R    = GeoMath.Hypot(X / 2, Y / 2);
                slam = R != 0 ? (Y / 2) / R : 0;
                clam = R != 0 ? (X / 2) / R : 1;
                double H = GeoMath.Hypot(Z / 2, R);
                sphi = (Z / 2) / H;
                cphi = R / H;
            }
            else if (_e4a == 0)
            {
                // Treat the spherical case.  Dealing with underflow in the general case
                // with _e2 = 0 is difficult.  Origin maps to N pole same as with
                // ellipsoid.
                double H = GeoMath.Hypot(h == 0 ? 1 : Z, R);
                sphi = (h == 0 ? 1 : Z) / H;
                cphi = R / H;
                h   -= _a;
            }
            else
            {
                // Treat prolate spheroids by swapping R and Z here and by switching
                // the arguments to phi = atan2(...) at the end.
                double
                    p = GeoMath.Square(R / _a),
                    q = _e2m * GeoMath.Square(Z / _a),
                    r = (p + q - _e4a) / 6;
                if (_f < 0)
                {
                    Utility.Swap(ref p, ref q);
                }
                if (!(_e4a * q == 0 && r <= 0))
                {
                    double
                    // Avoid possible division by zero when r = 0 by multiplying
                    // equations for s and t by r^3 and r, resp.
                        S    = _e4a * p * q / 4, // S = r^3 * s
                        r2   = GeoMath.Square(r),
                        r3   = r * r2,
                        disc = S * (2 * r3 + S);
                    double u = r;
                    if (disc >= 0)
                    {
                        double T3 = S + r3;
                        // Pick the sign on the sqrt to maximize abs(T3).  This minimizes
                        // loss of precision due to cancellation.  The result is unchanged
                        // because of the way the T is used in definition of u.
                        T3 += T3 < 0 ? -Math.Sqrt(disc) : Math.Sqrt(disc); // T3 = (r * t)^3
                                                                           // N.B. cbrt always returns the double root.  cbrt(-8) = -2.
                        double T = GeoMath.CubeRoot(T3);                   // T = r * t
                                                                           // T can be zero; but then r2 / T -> 0.
                        u += T + (T != 0 ? r2 / T : 0);
                    }
                    else
                    {
                        // T is complex, but the way u is defined the result is double.
                        double ang = Math.Atan2(Math.Sqrt(-disc), -(S + r3));
                        // There are three possible cube roots.  We choose the root which
                        // avoids cancellation.  Note that disc < 0 implies that r < 0.
                        u += 2 * r * Math.Cos(ang / 3);
                    }
                    double
                        v = Math.Sqrt(GeoMath.Square(u) + _e4a * q), // guaranteed positive
                                                                     // Avoid loss of accuracy when u < 0.  Underflow doesn't occur in
                                                                     // e4 * q / (v - u) because u ~ e^4 when q is small and u < 0.
                        uv = u < 0 ? _e4a * q / (v - u) : u + v,     // u+v, guaranteed positive
                                                                     // Need to guard against w going negative due to roundoff in uv - q.
                        w = Math.Max(0, _e2a * (uv - q) / (2 * v)),
                    // Rearrange expression for k to avoid loss of accuracy due to
                    // subtraction.  Division by 0 not possible because uv > 0, w >= 0.
                        k  = uv / (Math.Sqrt(uv + GeoMath.Square(w)) + w),
                        k1 = _f >= 0 ? k : k - _e2,
                        k2 = _f >= 0 ? k + _e2 : k,
                        d  = k1 * R / k2,
                        H  = GeoMath.Hypot(Z / k1, R / k2);
                    sphi = (Z / k1) / H;
                    cphi = (R / k2) / H;
                    h    = (1 - _e2m / k1) * GeoMath.Hypot(d, Z);
                }
                else
                {                  // e4 * q == 0 && r <= 0
                                   // This leads to k = 0 (oblate, equatorial plane) and k + e^2 = 0
                                   // (prolate, rotation axis) and the generation of 0/0 in the general
                                   // formulas for phi and h.  using the general formula and division by 0
                                   // in formula for h.  So handle this case by taking the limits:
                                   // f > 0: z -> 0, k      ->   e2 * Math.Sqrt(q)/Math.Sqrt(e4 - p)
                                   // f < 0: R -> 0, k + e2 -> - e2 * Math.Sqrt(q)/Math.Sqrt(e4 - p)
                    double
                        zz = Math.Sqrt((_f >= 0 ? _e4a - p : p) / _e2m),
                        xx = Math.Sqrt(_f < 0 ? _e4a - p : p),
                        H  = GeoMath.Hypot(zz, xx);
                    sphi = zz / H;
                    cphi = xx / H;
                    if (Z < 0)
                    {
                        sphi = -sphi;        // for tiny negative Z (not for prolate)
                    }
                    h = -_a * (_f >= 0 ? _e2m : 1) * H / _e2a;
                }
            }
            lat = GeoMath.Atan2d(sphi, cphi);
            lon = GeoMath.Atan2d(slam, clam);
            if (M != null)
            {
                Rotation(sphi, cphi, slam, clam, M);
            }
        }
        /**
         * Constructor for a ellipsoid with
         *
         * @param[in] a equatorial radius (meters).
         * @param[in] f flattening of ellipsoid.  Setting \e f = 0 gives a sphere.
         *   Negative \e f gives a prolate ellipsoid.
         * @param[in] k0 central scale factor.
         * @exception GeographicErr if \e a, (1 &minus; \e f) \e a, or \e k0 is
         *   not positive.
         **********************************************************************/
        public TransverseMercator(double a, double f, double k0)
        {
            _a   = a;
            _f   = f;
            _k0  = k0;
            _e2  = _f * (2 - _f);
            _es  = (f < 0 ? -1 : 1) * Math.Sqrt(Math.Abs(_e2));
            _e2m = 1 - _e2;
            // _c = Math.Sqrt( pow(1 + _e, 1 + _e) * pow(1 - _e, 1 - _e) ) )
            // See, for example, Lee (1976), p 100.
            _c = Math.Sqrt(_e2m) * Math.Exp(GeoMath.Eatanhe(1, _es));
            _n = _f / (2 - _f);


            if (!(GeoMath.IsFinite(_a) && _a > 0))
            {
                throw new GeographicException("Equatorial radius is not positive");
            }
            if (!(GeoMath.IsFinite(_f) && _f < 1))
            {
                throw new GeographicException("Polar semi-axis is not positive");
            }
            if (!(GeoMath.IsFinite(_k0) && _k0 > 0))
            {
                throw new GeographicException("Scale is not positive");
            }

            double[] b1coeff =
            {
                // b1*(n+1), polynomial in n2 of order 3
                1, 4, 64, 256, 256,
            };

            double[] alpcoeff =
            {
                // alp[1]/n^1, polynomial in n of order 5
                31564,        -66675,   34440,    47250, -100800,   75600, 151200,
                // alp[2]/n^2, polynomial in n of order 4
                -1983433,     863232,  748608, -1161216,  524160, 1935360,
                // alp[3]/n^3, polynomial in n of order 3
                670412,       406647, -533952,   184464,  725760,
                // alp[4]/n^4, polynomial in n of order 2
                6601661,    -7732800, 2230245,  7257600,
                // alp[5]/n^5, polynomial in n of order 1
                -13675556,   3438171, 7983360,
                // alp[6]/n^6, polynomial in n of order 0
                212378941, 319334400,
            };  // count = 27

            double[] betcoeff =
            {
                // bet[1]/n^1, polynomial in n of order 5
                384796,     -382725,    -6720,  932400, -1612800, 1209600, 2419200,
                // bet[2]/n^2, polynomial in n of order 4
                -1118711,   1695744, -1174656,  258048,    80640, 3870720,
                // bet[3]/n^3, polynomial in n of order 3
                22276,       -16929,   -15984,   12852,   362880,
                // bet[4]/n^4, polynomial in n of order 2
                -830251,    -158400,   197865, 7257600,
                // bet[5]/n^5, polynomial in n of order 1
                -435388,     453717, 15966720,
                // bet[6]/n^6, polynomial in n of order 0
                20648693, 638668800,
            };  // count = 27

            int m = maxpow_ / 2;

            _b1 = GeoMath.PolyVal(m, b1coeff, 0, GeoMath.Square(_n)) / (b1coeff[m + 1] * (1 + _n));
            // _a1 is the equivalent radius for computing the circumference of
            // ellipse.
            _a1 = _b1 * _a;
            int    o = 0;
            double d = _n;

            for (int l = 1; l <= maxpow_; ++l)
            {
                m       = maxpow_ - l;
                _alp[l] = d * GeoMath.PolyVal(m, alpcoeff, o, _n) / alpcoeff[o + m + 1];
                _bet[l] = d * GeoMath.PolyVal(m, betcoeff, o, _n) / betcoeff[o + m + 1];
                o      += m + 2;
                d      *= _n;
            }
            // Post condition: o == sizeof(alpcoeff) / sizeof(double) &&
            // o == sizeof(betcoeff) / sizeof(double)
        }
Beispiel #20
0
        private void LineInit(Geodesic g,
                              double lat1, double lon1,
                              double azi1, double salp1, double calp1,
                              int caps)
        {
            _lat1  = GeoMath.LatFix(lat1);
            _lon1  = lon1;
            _azi1  = azi1;
            _salp1 = salp1;
            _calp1 = calp1;
            _a     = g._a;
            _f     = g._f;
            _b     = g._b;
            _c2    = g._c2;
            _f1    = g._f1;
            // Always allow latitude and azimuth and unrolling of longitude
            _caps = ((int)caps | (int)GeodesicMask.LATITUDE | (int)GeodesicMask.AZIMUTH | (int)GeodesicMask.LONG_UNROLL); //TODO: check this

            double cbet1, sbet1;

            GeoMath.Sincosd(GeoMath.AngRound(_lat1), out sbet1, out cbet1); sbet1 *= _f1;
            // Ensure cbet1 = +epsilon at poles
            GeoMath.Norm(ref sbet1, ref cbet1);
            cbet1 = Math.Max(Geodesic.tiny_, cbet1);
            _dn1  = Math.Sqrt(1 + g._ep2 * GeoMath.Square(sbet1));

            // Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0),
            _salp0 = _salp1 * cbet1; // alp0 in [0, pi/2 - |bet1|]
                                     // Alt: calp0 = hypot(sbet1, calp1 * cbet1).  The following
                                     // is slightly better (consider the case salp1 = 0).
            _calp0 = GeoMath.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 Math.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(ref _ssig1, ref _csig1); // sig1 in (-pi, pi]
                                                  // Math::norm(_somg1, _comg1); -- don't need to normalize!

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

            if ((_caps & GeodesicMask.CAP_C1) != 0)
            {
                _C1a  = new double[nC1_ + 1];
                _A1m1 = GeodesicCoeff.A1m1f(eps);
                GeodesicCoeff.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];
                GeodesicCoeff.C1pf(eps, _C1pa);
            }

            if ((_caps & GeodesicMask.CAP_C2) != 0)
            {
                _C2a  = new double[nC2_ + 1];
                _A2m1 = GeodesicCoeff.A2m1f(eps);
                GeodesicCoeff.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.Square(_a) * _calp0 * _salp0 * g._e2;
                _B41 = Geodesic.SinCosSeries(false, _ssig1, _csig1, _C4a);
            }

            _a13 = _s13 = double.NaN;
        }
Beispiel #21
0
 /**
  * @return \e a1 the arc length (degrees) between the northward equatorial
  *   crossing and point 1.
  *
  * The result lies in (&minus;180&deg;, 180&deg;].
  **********************************************************************/
 public double EquatorialArc()
 {
     return(Init() ? GeoMath.Atan2d(_ssig1, _csig1) : double.NaN);
 }
Beispiel #22
0
 /**
  * @return \e azi0 the azimuth (degrees) of the geodesic line as it crosses
  *   the equator in a northward direction.
  *
  * The result lies in [&minus;90&deg;, 90&deg;].
  **********************************************************************/
 public double EquatorialAzimuth()
 {
     return(Init() ? GeoMath.Atan2d(_salp0, _calp0) : double.NaN);
 }
Beispiel #23
0
        ///@}

        /** \name The general position function.
         **********************************************************************/
        ///@{

        /**
         * The general position function.  GeodesicLine::Position and
         * GeodesicLine::ArcPosition are defined in terms of this function.
         *
         * @param[in] arcmode boolean flag determining the meaning of the second
         *   parameter; if \e arcmode is false, then the GeodesicLine object must
         *   have been constructed with \e caps |= GeodesicLine::DISTANCE_IN.
         * @param[in] s12_a12 if \e arcmode 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[in] outmask a bitor'ed combination of GeodesicLine::mask values
         *   specifying which of the following parameters should be set.
         * @param[out] lat2 GeodesicMask.LATITUDE of point 2 (degrees).
         * @param[out] lon2 GeodesicMask.LONGITUDE of point 2 (degrees); requires that the
         *   GeodesicLine object was constructed with \e caps |=
         *   GeodesicLine::GeodesicMask.LONGITUDE.
         * @param[out] azi2 (forward) GeodesicMask.AZIMUTH at point 2 (degrees).
         * @param[out] s12 distance from point 1 to point 2 (meters); requires
         *   that the GeodesicLine object was constructed with \e caps |=
         *   GeodesicLine::DISTANCE.
         * @param[out] m12 reduced length of geodesic (meters); requires that the
         *   GeodesicLine object was constructed with \e caps |=
         *   GeodesicLine::GeodesicMask.REDUCEDLENGTH.
         * @param[out] M12 geodesic scale of point 2 relative to point 1
         *   (dimensionless); requires that the GeodesicLine object was constructed
         *   with \e caps |= GeodesicLine::GeodesicMask.GEODESICSCALE.
         * @param[out] M21 geodesic scale of point 1 relative to point 2
         *   (dimensionless); requires that the GeodesicLine object was constructed
         *   with \e caps |= GeodesicLine::GeodesicMask.GEODESICSCALE.
         * @param[out] S12 GeodesicMask.AREA under the geodesic (meters<sup>2</sup>); requires
         *   that the GeodesicLine object was constructed with \e caps |=
         *   GeodesicLine::GeodesicMask.AREA.
         * @return \e a12 arc length from point 1 to point 2 (degrees).
         *
         * The GeodesicLine::mask values possible for \e outmask are
         * - \e outmask |= GeodesicLine::GeodesicMask.LATITUDE for the GeodesicMask.LATITUDE \e lat2;
         * - \e outmask |= GeodesicLine::GeodesicMask.LONGITUDE for the GeodesicMask.LATITUDE \e lon2;
         * - \e outmask |= GeodesicLine::GeodesicMask.AZIMUTH for the GeodesicMask.LATITUDE \e azi2;
         * - \e outmask |= GeodesicLine::DISTANCE for the distance \e s12;
         * - \e outmask |= GeodesicLine::GeodesicMask.REDUCEDLENGTH for the reduced length \e
         *   m12;
         * - \e outmask |= GeodesicLine::GeodesicMask.GEODESICSCALE for the geodesic scales \e
         *   M12 and \e M21;
         * - \e outmask |= GeodesicLine::GeodesicMask.AREA for the GeodesicMask.AREA \e S12;
         * - \e outmask |= GeodesicLine::ALL for all of the above;
         * - \e outmask |= GeodesicLine::LONG_UNROLL to unroll \e lon2 instead of
         *   reducing it into the range [&minus;180&deg;, 180&deg;].
         * .
         * Requesting a value which the GeodesicLine object is not capable of
         * computing is not an error; the corresponding argument will not be
         * altered.  Note, however, that the arc length is always computed and
         * returned as the function value.
         *
         * With the GeodesicLine::LONG_UNROLL bit seout t, the quantity \e lon2 &minus;
         * \e lon1 indicates how many times and in what sense the geodesic
         * encircles the ellipsoid.
         **********************************************************************/
        public double GenPosition(bool arcmode, double s12_a12, int 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)
        {
            outmask &= (_caps & GeodesicMask.OUT_MASK);

            lat2 = double.NaN;
            lon2 = double.NaN;
            azi2 = double.NaN;
            s12  = double.NaN;
            m12  = double.NaN;
            M12  = double.NaN;
            M21  = double.NaN;
            S12  = double.NaN;

            if (!(Init() && (arcmode || (_caps & (GeodesicMask.OUT_MASK & GeodesicMask.DISTANCE_IN)) != 0)))
            {
                // 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 * GeoMath.Degree;
                GeoMath.Sincosd(s12_a12, out ssig12, out csig12);
            }
            else
            {
                // Interpret s12_a12 as distance
                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
                }
            }

            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)
            {
                s12 = arcmode ? _b * ((1 + _A1m1) * sig12 + AB1) : s12_a12;
            }

            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-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 = lam12 / GeoMath.Degree;
                lon2 = (outmask & GeodesicMask.LONG_UNROLL) != 0 ? _lon1 + lon12 :
                       GeoMath.AngNormalize(GeoMath.AngNormalize(_lon1) +
                                            GeoMath.AngNormalize(lon12));
            }

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

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

            return(arcmode ? s12_a12 : sig12 / GeoMath.Degree);
        }
Beispiel #24
0
        /**
         * Forward projection, from geographic to Cassini-Soldner.
         *
         * @param[in] lat latitude of point (degrees).
         * @param[in] lon longitude of point (degrees).
         * @param[out] x easting of point (meters).
         * @param[out] y northing of point (meters).
         * @param[out] azi azimuth of easting direction at point (degrees).
         * @param[out] rk reciprocal of azimuthal northing scale at point.
         *
         * \e lat should be in the range [&minus;90&deg;, 90&deg;].  A call to
         * Forward followed by a call to Reverse will return the original (\e lat,
         * \e lon) (to within roundoff).  The routine does nothing if the origin
         * has not been set.
         **********************************************************************/
        public void Forward(double lat, double lon,
                            out double x, out double y, out double azi, out double rk)
        {
            if (!Init())
            {
                x   = double.NaN;
                y   = double.NaN;
                azi = double.NaN;
                rk  = double.NaN;
            }

            double e;
            double dlon = GeoMath.AngDiff(LongitudeOrigin(), lon, out e);
            double sig12, s12, azi1, azi2;

            sig12  = _earth.Inverse(lat, -Math.Abs(dlon), lat, Math.Abs(dlon), out s12, out azi1, out azi2);
            sig12 *= 0.5;
            s12   *= 0.5;
            if (s12 == 0)
            {
                double da = GeoMath.AngDiff(azi1, azi2, out e) / 2;
                if (Math.Abs(dlon) <= 90)
                {
                    azi1 = 90 - da;
                    azi2 = 90 + da;
                }
                else
                {
                    azi1 = -90 - da;
                    azi2 = -90 + da;
                }
            }
            if (dlon < 0)
            {
                azi2  = azi1;
                s12   = -s12;
                sig12 = -sig12;
            }
            x   = s12;
            azi = GeoMath.AngNormalize(azi2);
            GeodesicLine perp = _earth.Line(lat, dlon, azi, GeodesicMask.GEODESICSCALE);
            double       t;

            perp.GenPosition(true, -sig12,
                             GeodesicMask.GEODESICSCALE,
                             out t, out t, out t, out t, out t, out t, out rk, out t);

            double salp0, calp0;

            GeoMath.Sincosd(perp.EquatorialAzimuth(), out salp0, out calp0);
            double
                sbet1  = lat >= 0 ? calp0 : -calp0,
                cbet1  = Math.Abs(dlon) <= 90 ? Math.Abs(salp0) : -Math.Abs(salp0),
                sbet01 = sbet1 * _cbet0 - cbet1 * _sbet0,
                cbet01 = cbet1 * _cbet0 + sbet1 * _sbet0,
                sig01  = Math.Atan2(sbet01, cbet01) / GeoMath.Degree;

            _meridian.GenPosition(true, sig01,
                                  GeodesicMask.DISTANCE,
                                  out t, out t, out t, out y, out t, out t, out t, out t);
        }
 static double hyp(double x)
 {
     return(GeoMath.Hypot(1, x));
 }
        /**
         * Reverse projection, from transverse Mercator to geographic.
         *
         * @param[in] lon0 central meridian of the projection (degrees).
         * @param[in] x easting of point (meters).
         * @param[in] y northing of point (meters).
         * @param[out] lat latitude of point (degrees).
         * @param[out] lon longitude of point (degrees).
         * @param[out] gamma meridian convergence at point (degrees).
         * @param[out] k scale of projection at point.
         *
         * No false easting or northing is added.  The value of \e lon returned is
         * in the range [&minus;180&deg;, 180&deg;].
         **********************************************************************/
        public void Reverse(double lon0, double x, double y,
                            out double lat, out double lon, out double gamma, out double k)
        {
            // This undoes the steps in Forward.  The wrinkles are: (1) Use of the
            // reverted series to express zeta' in terms of zeta. (2) Newton's method
            // to solve for phi in terms of tan(phi).
            double
                xi  = y / (_a1 * _k0),
                eta = x / (_a1 * _k0);
            // Explicitly enforce the parity
            int
                xisign  = (xi < 0) ? -1 : 1,
                etasign = (eta < 0) ? -1 : 1;

            xi  *= xisign;
            eta *= etasign;
            bool backside = xi > Math.PI / 2;

            if (backside)
            {
                xi = Math.PI - xi;
            }
            double
                c0 = Math.Cos(2 * xi), ch0 = Math.Cosh(2 * eta),
                s0 = Math.Sin(2 * xi), sh0 = Math.Sinh(2 * eta);


            int     n  = maxpow_;
            Complex a  = new Complex(2 * c0 * ch0, -2 * s0 * sh0); // 2 * Math.Cos(2*zeta')
            Complex y0 = new Complex((n & 1) != 0 ? -_bet[n] : 0, 0);
            Complex y1;                                            // default initializer is 0+i0
            Complex z0 = new Complex((n & 1) != 0 ? -2 * n * _bet[n] : 0, 0);
            Complex z1;

            if ((n & 1) != 0)
            {
                --n;
            }
            while (n > 0)
            {
                y1 = a * y0 - y1 - _bet[n];
                z1 = a * z0 - z1 - 2 * n * _bet[n];
                --n;
                y0 = a * y1 - y0 - _bet[n];
                z0 = a * z1 - z0 - 2 * n * _bet[n];
                --n;
            }
            a /= 2;                               // Math.Cos(2*zeta)
            z1 = 1 - z1 + a * z0;
            a  = new Complex(s0 * ch0, c0 * sh0); // Math.Sin(2*zeta)
            y1 = new Complex(xi, eta) + a * y0;
            // Convergence and scale for Gauss-Schreiber TM to Gauss-Krueger TM.
            gamma = GeoMath.Atan2d(z1.Imaginary, z1.Real);
            k     = _b1 / Complex.Abs(z1);
            // JHS 154 has
            //
            //   phi' = asin(Math.Sin(xi') / Math.Cosh(eta')) (Krueger p 17 (25))
            //   lam = asin(tanh(eta') / Math.Cos(phi')
            //   psi = asinh(tan(phi'))
            double
                xip = y1.Real, etap = y1.Imaginary,
                s = Math.Sinh(etap),
                c = Math.Max(0, Math.Cos(xip)), // Math.Cos(pi/2) might be negative
                r = GeoMath.Hypot(s, c);

            if (r != 0)
            {
                lon = GeoMath.Atan2d(s, c); // Krueger p 17 (25)
                                            // Use Newton's method to solve for tau
                double
                    sxip = Math.Sin(xip),
                    tau  = GeoMath.Tauf(sxip / r, _es);
                gamma += GeoMath.Atan2d(sxip * Math.Tanh(etap), c); // Krueger p 19 (31)
                lat    = GeoMath.Atand(tau);
                // Note Math.Cos(phi') * Math.Cosh(eta') = r
                k *= Math.Sqrt(_e2m + _e2 / (1 + GeoMath.Square(tau))) *
                     GeoMath.Hypot(1, tau) * r;
            }
            else
            {
                lat = 90;
                lon = 0;
                k  *= _c;
            }
            lat *= xisign;
            if (backside)
            {
                lon = 180 - lon;
            }
            lon *= etasign;
            lon  = GeoMath.AngNormalize(lon + lon0);
            if (backside)
            {
                gamma = 180 - gamma;
            }
            gamma *= xisign * etasign;
            gamma  = GeoMath.AngNormalize(gamma);
            k     *= _k0;
        }
        void Init(double sphi1, double cphi1, double sphi2, double cphi2, double k1)
        {
            {
                double r;
                r      = GeoMath.Hypot(sphi1, cphi1);
                sphi1 /= r; cphi1 /= r;
                r      = GeoMath.Hypot(sphi2, cphi2);
                sphi2 /= r; cphi2 /= r;
            }
            bool polar = (cphi1 == 0);

            cphi1 = Math.Max(epsx_, cphi1);   // Avoid singularities at poles
            cphi2 = Math.Max(epsx_, cphi2);
            // Determine hemisphere of tangent latitude
            _sign = sphi1 + sphi2 >= 0 ? 1 : -1;
            // Internally work with tangent latitude positive
            sphi1 *= _sign; sphi2 *= _sign;
            if (sphi1 > sphi2)
            {
                Utility.Swap(ref sphi1, ref sphi2); Utility.Swap(ref cphi1, ref cphi2); // Make phi1 < phi2
            }
            double
                tphi1 = sphi1 / cphi1, tphi2 = sphi2 / cphi2;

            // q = (1-e^2)*(sphi/(1-e^2*sphi^2) - atanhee(sphi))
            // qZ = q(pi/2) = (1 + (1-e^2)*atanhee(1))
            // atanhee(x) = atanh(e*x)/e
            // q = sxi * qZ
            // dq/dphi = 2*(1-e^2)*cphi/(1-e^2*sphi^2)^2
            //
            // n = (m1^2-m2^2)/(q2-q1) -> sin(phi0) for phi1, phi2 -> phi0
            // C = m1^2 + n*q1 = (m1^2*q2-m2^2*q1)/(q2-q1)
            // let
            //   rho(pi/2)/rho(-pi/2) = (1-s)/(1+s)
            //   s = n*qZ/C
            //     = qZ * (m1^2-m2^2)/(m1^2*q2-m2^2*q1)
            //     = qZ * (scbet2^2 - scbet1^2)/(scbet2^2*q2 - scbet1^2*q1)
            //     = (scbet2^2 - scbet1^2)/(scbet2^2*sxi2 - scbet1^2*sxi1)
            //     = (tbet2^2 - tbet1^2)/(scbet2^2*sxi2 - scbet1^2*sxi1)
            // 1-s = -((1-sxi2)*scbet2^2 - (1-sxi1)*scbet1^2)/
            //         (scbet2^2*sxi2 - scbet1^2*sxi1)
            //
            // Define phi0 to give same value of s, i.e.,
            //  s = sphi0 * qZ / (m0^2 + sphi0*q0)
            //    = sphi0 * scbet0^2 / (1/qZ + sphi0 * scbet0^2 * sxi0)

            double tphi0, C;

            if (polar || tphi1 == tphi2)
            {
                tphi0 = tphi2;
                C     = 1;                // ignored
            }
            else
            {
                double
                    tbet1 = _fm * tphi1, scbet12 = 1 + GeoMath.Square(tbet1),
                    tbet2 = _fm * tphi2, scbet22 = 1 + GeoMath.Square(tbet2),
                    txi1 = txif(tphi1), cxi1 = 1 / hyp(txi1), sxi1 = txi1 * cxi1,
                    txi2 = txif(tphi2), cxi2 = 1 / hyp(txi2), sxi2 = txi2 * cxi2,
                    dtbet2 = _fm * (tbet1 + tbet2),
                    es1 = 1 - _e2 * GeoMath.Square(sphi1), es2 = 1 - _e2 * GeoMath.Square(sphi2),

                /*
                 * dsxi = ( (_e2 * sq(sphi2 + sphi1) + es2 + es1) / (2 * es2 * es1) +
                 *       Datanhee(sphi2, sphi1) ) * Dsn(tphi2, tphi1, sphi2, sphi1) /
                 * ( 2 * _qx ),
                 */
                    dsxi = ((1 + _e2 * sphi1 * sphi2) / (es2 * es1) +
                            Datanhee(sphi2, sphi1)) * Dsn(tphi2, tphi1, sphi2, sphi1) /
                           (2 * _qx),
                    den = (sxi2 + sxi1) * dtbet2 + (scbet22 + scbet12) * dsxi,
                // s = (sq(tbet2) - sq(tbet1)) / (scbet22*sxi2 - scbet12*sxi1)
                    s = 2 * dtbet2 / den,
                // 1-s = -(sq(scbet2)*(1-sxi2) - sq(scbet1)*(1-sxi1)) /
                //        (scbet22*sxi2 - scbet12*sxi1)
                // Write
                //   sq(scbet)*(1-sxi) = sq(scbet)*(1-sphi) * (1-sxi)/(1-sphi)
                    sm1 = -Dsn(tphi2, tphi1, sphi2, sphi1) *
                          (-(((sphi2 <= 0 ? (1 - sxi2) / (1 - sphi2) :
                               GeoMath.Square(cxi2 / cphi2) * (1 + sphi2) / (1 + sxi2)) +
                              (sphi1 <= 0 ? (1 - sxi1) / (1 - sphi1) :
                               GeoMath.Square(cxi1 / cphi1) * (1 + sphi1) / (1 + sxi1)))) *
                           (1 + _e2 * (sphi1 + sphi2 + sphi1 * sphi2)) /
                           (1 + (sphi1 + sphi2 + sphi1 * sphi2)) +
                           (scbet22 * (sphi2 <= 0 ? 1 - sphi2 :
                                       GeoMath.Square(cphi2) / (1 + sphi2)) +
                            scbet12 * (sphi1 <= 0 ? 1 - sphi1 : GeoMath.Square(cphi1) / (1 + sphi1)))
                           * (_e2 * (1 + sphi1 + sphi2 + _e2 * sphi1 * sphi2) / (es1 * es2)
                              + _e2m * DDatanhee(sphi1, sphi2)) / _qZ) / den;
                // C = (scbet22*sxi2 - scbet12*sxi1) / (scbet22 * scbet12 * (sx2 - sx1))
                C     = den / (2 * scbet12 * scbet22 * dsxi);
                tphi0 = (tphi2 + tphi1) / 2;
                double stol = tol0_ * Math.Max(1, Math.Abs(tphi0));
                for (int i = 0; i < 2 * numit0_; ++i)
                {
                    // Solve (scbet0^2 * sphi0) / (1/qZ + scbet0^2 * sphi0 * sxi0) = s
                    // for tphi0 by Newton's method on
                    // v(tphi0) = (scbet0^2 * sphi0) - s * (1/qZ + scbet0^2 * sphi0 * sxi0)
                    //          = 0
                    // Alt:
                    // (scbet0^2 * sphi0) / (1/qZ - scbet0^2 * sphi0 * (1-sxi0))
                    //          = s / (1-s)
                    // w(tphi0) = (1-s) * (scbet0^2 * sphi0)
                    //             - s  * (1/qZ - scbet0^2 * sphi0 * (1-sxi0))
                    //          = (1-s) * (scbet0^2 * sphi0)
                    //             - S/qZ  * (1 - scbet0^2 * sphi0 * (qZ-q0))
                    // Now
                    // qZ-q0 = (1+e2*sphi0)*(1-sphi0)/(1-e2*sphi0^2) +
                    //         (1-e2)*atanhee((1-sphi0)/(1-e2*sphi0))
                    // In limit sphi0 -> 1, qZ-q0 -> 2*(1-sphi0)/(1-e2), so wrte
                    // qZ-q0 = 2*(1-sphi0)/(1-e2) + A + B
                    // A = (1-sphi0)*( (1+e2*sphi0)/(1-e2*sphi0^2) - (1+e2)/(1-e2) )
                    //   = -e2 *(1-sphi0)^2 * (2+(1+e2)*sphi0) / ((1-e2)*(1-e2*sphi0^2))
                    // B = (1-e2)*atanhee((1-sphi0)/(1-e2*sphi0)) - (1-sphi0)
                    //   = (1-sphi0)*(1-e2)/(1-e2*sphi0)*
                    //     ((atanhee(x)/x-1) - e2*(1-sphi0)/(1-e2))
                    // x = (1-sphi0)/(1-e2*sphi0), atanhee(x)/x = atanh(e*x)/(e*x)
                    //
                    // 1 - scbet0^2 * sphi0 * (qZ-q0)
                    //   = 1 - scbet0^2 * sphi0 * (2*(1-sphi0)/(1-e2) + A + B)
                    //   = D - scbet0^2 * sphi0 * (A + B)
                    // D = 1 - scbet0^2 * sphi0 * 2*(1-sphi0)/(1-e2)
                    //   = (1-sphi0)*(1-e2*(1+2*sphi0*(1+sphi0)))/((1-e2)*(1+sphi0))
                    // dD/dsphi0 = -2*(1-e2*sphi0^2*(2*sphi0+3))/((1-e2)*(1+sphi0)^2)
                    // d(A+B)/dsphi0 = 2*(1-sphi0^2)*e2*(2-e2*(1+sphi0^2))/
                    //                 ((1-e2)*(1-e2*sphi0^2)^2)

                    double
                        scphi02 = 1 + GeoMath.Square(tphi0), scphi0 = Math.Sqrt(scphi02),
                    // sphi0m = 1-sin(phi0) = 1/( sec(phi0) * (tan(phi0) + sec(phi0)) )
                        sphi0 = tphi0 / scphi0, sphi0m = 1 / (scphi0 * (tphi0 + scphi0)),
                    // scbet0^2 * sphi0
                        g = (1 + GeoMath.Square(_fm * tphi0)) * sphi0,
                    // dg/dsphi0 = dg/dtphi0 * scphi0^3
                        dg = _e2m * scphi02 * (1 + 2 * GeoMath.Square(tphi0)) + _e2,
                        D  = sphi0m * (1 - _e2 * (1 + 2 * sphi0 * (1 + sphi0))) / (_e2m * (1 + sphi0)),
                    // dD/dsphi0
                        dD = -2 * (1 - _e2 * GeoMath.Square(sphi0) * (2 * sphi0 + 3)) /
                             (_e2m * GeoMath.Square(1 + sphi0)),
                        A = -_e2 *GeoMath.Square(sphi0m) * (2 + (1 + _e2) * sphi0) /
                            (_e2m * (1 - _e2 * GeoMath.Square(sphi0))),
                        B = (sphi0m * _e2m / (1 - _e2 * sphi0) *
                             (atanhxm1(_e2 *
                                       GeoMath.Square(sphi0m / (1 - _e2 * sphi0))) - _e2 * sphi0m / _e2m)),
                    // d(A+B)/dsphi0
                        dAB = (2 * _e2 * (2 - _e2 * (1 + GeoMath.Square(sphi0))) /
                               (_e2m * GeoMath.Square(1 - _e2 * GeoMath.Square(sphi0)) * scphi02)),
                        u = sm1 * g - s / _qZ * (D - g * (A + B)),
                    // du/dsphi0
                        du  = sm1 * dg - s / _qZ * (dD - dg * (A + B) - g * dAB),
                        dtu = -u / du * (scphi0 * scphi02);

                    tphi0 += dtu;
                    if (!(Math.Abs(dtu) >= stol))
                    {
                        break;
                    }
                }
            }
            _txi0  = txif(tphi0); _scxi0 = hyp(_txi0); _sxi0 = _txi0 / _scxi0;
            _n0    = tphi0 / hyp(tphi0);
            _m02   = 1 / (1 + GeoMath.Square(_fm * tphi0));
            _nrho0 = polar ? 0 : _a *Math.Sqrt(_m02);

            _k0   = Math.Sqrt(tphi1 == tphi2 ? 1 : C / (_m02 + _n0 * _qZ * _sxi0)) * k1;
            _k2   = GeoMath.Square(_k0);
            _lat0 = _sign * Math.Atan(tphi0) / GeoMath.Degree;
        }