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