/// <summary> /// Forward projection, from geographic to Cassini-Soldner. /// </summary> /// <param name="lat">latitude of point (degrees).</param> /// <param name="lon">longitude of point (degrees).</param> /// <param name="azi">azimuth of easting direction at point (degrees).</param> /// <param name="rk">reciprocal of azimuthal northing scale at point.</param> /// <returns> /// <i>x</i>, easting of point and <i>y</i>, northing of point, in meters. /// </returns> /// <remarks> /// <paramref name="lat"/> should be in the range [−90°, 90°]. /// A call to <see cref="Forward(double, double, out double, out double)"/> /// followed by a call to <see cref="Reverse(double, double, out double, out double)"/> /// will return the original (<paramref name="lat"/>, <paramref name="lon"/>) (to within roundoff). /// The routine does nothing if the origin has not been set. /// </remarks> public (double x, double y) Forward(double lat, double lon, out double azi, out double rk) { var dlon = AngDiff(LongitudeOrigin, lon); var sig12 = _earth.Inverse(lat, -Abs(dlon), lat, Abs(dlon), out var s12, out var azi1, out var azi2); sig12 *= 0.5; s12 *= 0.5; if (s12 == 0) { var da = AngDiff(azi1, azi2) / 2; if (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; } var x = s12; azi = AngNormalize(azi2); var perp = _earth.Line(lat, dlon, azi, GeodesicFlags.GeodesicScale); perp.GenPosition(true, -sig12, GeodesicFlags.GeodesicScale, out _, out _, out _, out _, out _, out _, out rk, out _); SinCosd(perp.EquatorialAzimuth, out var salp0, out var calp0); double sbet1 = lat >= 0 ? calp0 : -calp0, cbet1 = Abs(dlon) <= 90 ? Abs(salp0) : -Abs(salp0), sbet01 = sbet1 * _cbet0 - cbet1 * _sbet0, cbet01 = cbet1 * _cbet0 + sbet1 * _sbet0, sig01 = Atan2(sbet01, cbet01) / Degree; _meridian.GenPosition(true, sig01, GeodesicFlags.Distance, out _, out _, out _, out var y, out _, out _, out _, out _); return(x, y); }