internal double txif(double tphi) { // sxi = ( sphi/(1-e2*sphi^2) + atanhee(sphi) ) / // ( 1/(1-e2) + atanhee(1) ) // // txi = ( sphi/(1-e2*sphi^2) + atanhee(sphi) ) / // sqrt( ( (1+e2*sphi)*(1-sphi)/( (1-e2*sphi^2) * (1-e2) ) + // atanhee((1-sphi)/(1-e2*sphi)) ) * // ( (1-e2*sphi)*(1+sphi)/( (1-e2*sphi^2) * (1-e2) ) + // atanhee((1+sphi)/(1+e2*sphi)) ) ) // // subst 1-sphi = cphi^2/(1+sphi) int s = tphi < 0 ? -1 : 1; // Enforce odd parity tphi *= s; double cphi2 = 1 / (1 + GeoMath.Square(tphi)), sphi = tphi * Math.Sqrt(cphi2), es1 = _e2 * sphi, es2m1 = 1 - es1 * sphi, sp1 = 1 + sphi, es1m1 = (1 - es1) * sp1, es2m1a = _e2m * es2m1, es1p1 = sp1 / (1 + es1); return(s * (sphi / es2m1 + atanhee(sphi)) / Math.Sqrt((cphi2 / (es1p1 * es2m1a) + atanhee(cphi2 / es1m1)) * (es1m1 / es2m1a + atanhee(es1p1)))); }
internal double tphif(double txi) { double tphi = txi, stol = tol_ * Math.Max(1, Math.Abs(txi)); // CHECK: min iterations = 1, max iterations = 2; mean = 1.99 for (int i = 0; i < numit_; ++i) { // dtxi/dtphi = (scxi/scphi)^3 * 2*(1-e^2)/(qZ*(1-e^2*sphi^2)^2) double txia = txif(tphi), tphi2 = GeoMath.Square(tphi), scphi2 = 1 + tphi2, scterm = scphi2 / (1 + GeoMath.Square(txia)), dtphi = (txi - txia) * scterm * Math.Sqrt(scterm) * _qx * GeoMath.Square(1 - _e2 * tphi2 / scphi2); tphi += dtphi; if (!(Math.Abs(dtphi) >= stol)) { break; } } return(tphi); }
// The coefficients C2[l] in the Fourier expansion of B2 public static void C2f(double eps, double[] c) { double[] coeff = { // C2[1]/eps^1, polynomial in eps2 of order 2 1, 2, 16, 32, // C2[2]/eps^2, polynomial in eps2 of order 2 35, 64, 384, 2048, // C2[3]/eps^3, polynomial in eps2 of order 1 15, 80, 768, // C2[4]/eps^4, polynomial in eps2 of order 1 7, 35, 512, // C2[5]/eps^5, polynomial in eps2 of order 0 63, 1280, // C2[6]/eps^6, polynomial in eps2 of order 0 77, 2048, }; double eps2 = GeoMath.Square(eps), d = eps; int o = 0; for (int l = 1; l <= nC2_; ++l) { // l is index of C2[l] int m = (nC2_ - l) / 2; // order of polynomial in eps^2 c[l] = d * GeoMath.PolyVal(m, coeff, o, eps2) / coeff[o + m + 1]; o += m + 2; d *= eps; } }
/** * Forward projection, from geographic to Lambert conformal conic. * * @param[in] lon0 central meridian longitude (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 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 and \e lat should be in the range * [−90°, 90°]. The values of \e x and \e y returned for * points which project to infinity (i.e., one or both of the poles) will * be large but finite. **********************************************************************/ public void Forward(double lon0, double lat, double lon, out double x, out double y, out double gamma, out double k) { double e; lon = GeoMath.AngDiff(lon0, lon, out e); lat *= _sign; double sphi, cphi; GeoMath.Sincosd(GeoMath.LatFix(lat) * _sign, out sphi, out cphi); cphi = Math.Max(epsx_, cphi); double lam = lon * GeoMath.Degree, tphi = sphi / cphi, txi = txif(tphi), sxi = txi / hyp(txi), dq = _qZ * Dsn(txi, _txi0, sxi, _sxi0) * (txi - _txi0), drho = -_a * dq / (Math.Sqrt(_m02 - _n0 * dq) + _nrho0 / _a), theta = _k2 * _n0 * lam, stheta = Math.Sin(theta), ctheta = Math.Cos(theta), t = _nrho0 + _n0 * drho; x = t * (_n0 != 0 ? stheta / _n0 : _k2 * lam) / _k0; y = (_nrho0 * (_n0 != 0 ? (ctheta < 0 ? 1 - ctheta : GeoMath.Square(stheta) / (1 + ctheta)) / _n0 : 0) - drho * ctheta) / _k0; k = _k0 * (t != 0 ? t * hyp(_fm * tphi) / _a : 1); y *= _sign; gamma = _sign * theta / GeoMath.Degree; }
// The coefficients C1p[l] in the Fourier expansion of B1p public static void C1pf(double eps, double[] c) { double[] coeff = { // C1p[1]/eps^1, polynomial in eps2 of order 2 205, -432, 768, 1536, // C1p[2]/eps^2, polynomial in eps2 of order 2 4005, -4736, 3840, 12288, // C1p[3]/eps^3, polynomial in eps2 of order 1 -225, 116, 384, // C1p[4]/eps^4, polynomial in eps2 of order 1 -7173, 2695, 7680, // C1p[5]/eps^5, polynomial in eps2 of order 0 3467, 7680, // C1p[6]/eps^6, polynomial in eps2 of order 0 38081, 61440, }; double eps2 = GeoMath.Square(eps); double d = eps;; int o = 0; for (int l = 1; l <= nC1p_; ++l) { // l is index of C1p[l] int m = (nC1p_ - l) / 2; // order of polynomial in eps^2 c[l] = d * GeoMath.PolyVal(m, coeff, o, eps2) / coeff[o + m + 1]; o += m + 2; d *= eps; } }
// The coefficients C1[l] in the Fourier expansion of B1 public static void C1f(double eps, double[] c) { double[] coeff = { // C1[1]/eps^1, polynomial in eps2 of order 2 -1, 6, -16, 32, // C1[2]/eps^2, polynomial in eps2 of order 2 -9, 64, -128, 2048, // C1[3]/eps^3, polynomial in eps2 of order 1 9, -16, 768, // C1[4]/eps^4, polynomial in eps2 of order 1 3, -5, 512, // C1[5]/eps^5, polynomial in eps2 of order 0 -7, 1280, // C1[6]/eps^6, polynomial in eps2 of order 0 -7, 2048, }; double eps2 = GeoMath.Square(eps); double d = eps; int o = 0; for (int l = 1; l <= nC1_; ++l) { // l is index of C1p[l] int m = (nC1_ - l) / 2; // order of polynomial in eps^2 c[l] = d * GeoMath.PolyVal(m, coeff, o, eps2) / coeff[o + m + 1]; o += m + 2; d *= eps; } }
// Divided differences // Definition: Df(x,y) = (f(x)-f(y))/(x-y) // See: // W. M. Kahan and R. J. Fateman, // Symbolic computation of divided differences, // SIGSAM Bull. 33(3), 7-28 (1999) // https://doi.org/10.1145/334714.334716 // http://www.cs.berkeley.edu/~fateman/papers/divdiff.pdf // // General rules // h(x) = f(g(x)): Dh(x,y) = Df(g(x),g(y))*Dg(x,y) // h(x) = f(x)*g(x): // Dh(x,y) = Df(x,y)*g(x) + Dg(x,y)*f(y) // = Df(x,y)*g(y) + Dg(x,y)*f(x) // = Df(x,y)*(g(x)+g(y))/2 + Dg(x,y)*(f(x)+f(y))/2 // // sn(x) = x/GeoMath.Square(1+x^2): Dsn(x,y) = (x+y)/((sn(x)+sn(y))*(1+x^2)*(1+y^2)) static double Dsn(double x, double y, double sx, double sy) { // sx = x/hyp(x) double t = x * y; return(t > 0 ? (x + y) * GeoMath.Square((sx * sy) / t) / (sx + sy) : (x - y != 0 ? (sx - sy) / (x - y) : 1)); }
// 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)); }
// 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); }
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 − \e f) \e a, or \e k1 is * not positive. * @exception new GeographicException if \e stdlat1 or \e stdlat2 is not in * [−90°, 90°], 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); }
/** * 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 − \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"); } }
/** * 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 [−180°, 180°]. The value of \e lat returned is * in the range [−90°, 90°]. 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); }
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); } }
/** * 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 * [−90°, 90°]. **********************************************************************/ 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; }
/** * 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 − \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) }
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; }
///@} /** \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 [−180°, 180°]. * . * 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 − * \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); }
/** * 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 [−180°, 180°]. **********************************************************************/ 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; }