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