/** * 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; }