public void SetDistance(double s13) { _s13 = s13; GeodesicData g = Position(false, _s13, 0); _a13 = g.a12; }
void SetArc(double a13) { _a13 = a13; GeodesicData g = Position(true, _a13, GeodesicMask.DISTANCE); _s13 = g.s12; }
/** * Reverse projection, from gnomonic to geographic. * <p> * @param lat0 latitude of center point of projection (degrees). * @param lon0 longitude of center point of projection (degrees). * @param x easting of point (meters). * @param y northing of point (meters). * @return {@link GnomonicData} object with the following fields: * <i>lat0</i>, <i>lon0</i>, <i>lat</i>, <i>lon</i>, <i>x</i>, <i>y</i>, * <i>azi</i>, <i>rk</i>. * <p> * <i>lat0</i> should be in the range [−90°, 90°] and * <i>lon0</i> should be in the range [−540°, 540°). * <i>lat</i> will be in the range [−90°, 90°] and <i>lon</i> * will be in the range [−180°, 180°]. The scale of the * projection is 1/<i>rk<sup>2</sup></i> in the "radial" direction, * <i>azi</i> clockwise from true north, and is 1/<i>rk</i> in the direction * perpendicular to this. Even though all inputs should return a valid * <i>lat</i> and <i>lon</i>, it's possible that the procedure fails to * converge for very large <i>x</i> or <i>y</i>; in this case NaNs are * returned for all the output arguments. A call to Reverse followed by a * call to Forward will return the original (<i>x</i>, <i>y</i>) (to * roundoff). */ public GnomonicData Reverse(double lat0, double lon0, double x, double y) { var azi0 = GeoMath.Atan2d(x, y); var rho = GeoMath.Hypot(x, y); var s = this.MajorRadius * Math.Atan(rho / this.MajorRadius); var little = rho <= this.MajorRadius; if (!little) { rho = 1 / rho; } var line = Earth.Line(lat0, lon0, azi0, GeodesicMask.LATITUDE | GeodesicMask.LONGITUDE | GeodesicMask.AZIMUTH | GeodesicMask.DISTANCE_IN | GeodesicMask.REDUCEDLENGTH | GeodesicMask.GEODESICSCALE); int count = numit_, trip = 0; GeodesicData pos = GeodesicData.NaN; while (count-- > 0) { pos = line.Position(s, GeodesicMask.LONGITUDE | GeodesicMask.LATITUDE | GeodesicMask.AZIMUTH | GeodesicMask.DISTANCE_IN | GeodesicMask.REDUCEDLENGTH | GeodesicMask.GEODESICSCALE); if (trip > 0) { break; } double ds = little ? ((pos.m12 / pos.M12) - rho) * pos.M12 * pos.M12 : (rho - (pos.M12 / pos.m12)) * pos.m12 * pos.m12; s -= ds; if (Math.Abs(ds) <= eps_ * this.MajorRadius) { trip++; } } if (trip == 0) { return(new GnomonicData(lat0, lon0, Double.NaN, Double.NaN, x, y, Double.NaN, Double.NaN)); } else { return(new GnomonicData(lat0, lon0, pos.lat2, pos.lon2, x, y, pos.azi2, pos.M12)); } }
/** * Return the results so far. * <p> * @param reverse if true then clockwise (instead of counter-clockwise) * traversal counts as a positive area. * @param sign if true then return a signed result for the area if * the polygon is traversed in the "wrong" direction instead of returning * the area for the rest of the earth. * @return PolygonResult(<i>num</i>, <i>perimeter</i>, <i>area</i>) where * <i>num</i> is the number of vertices, <i>perimeter</i> is the perimeter * of the polygon or the length of the polyline (meters), and <i>area</i> * is the area of the polygon (meters<sup>2</sup>) or Double.NaN of * <i>polyline</i> is true in the constructor. * <p> * More points can be added to the polygon after this call. **********************************************************************/ public PolygonResult Compute(bool reverse, bool sign) { if (_num < 2) { return(new PolygonResult(_num, 0, _polyline ? Double.NaN : 0)); } if (_polyline) { return(new PolygonResult(_num, _perimetersum.Sum(), Double.NaN)); } GeodesicData g = _earth.Inverse(_lat1, _lon1, _lat0, _lon0, _mask); Accumulator tempsum = new Accumulator(_areasum); tempsum.Add(g.S12); int crossings = _crossings + transit(_lon1, _lon0); if ((crossings & 1) != 0) { tempsum.Add((tempsum.Sum() < 0 ? 1 : -1) * _area0 / 2); } // area is with the clockwise sense. If !reverse convert to // counter-clockwise convention. if (!reverse) { tempsum.Negate(); } // If sign put area in (-area0/2, area0/2], else put area in [0, area0) if (sign) { if (tempsum.Sum() > _area0 / 2) { tempsum.Add(-_area0); } else if (tempsum.Sum() <= -_area0 / 2) { tempsum.Add(+_area0); } } else { if (tempsum.Sum() >= _area0) { tempsum.Add(-_area0); } else if (tempsum.Sum() < 0) { tempsum.Add(+_area0); } } return (new PolygonResult(_num, _perimetersum.Sum(g.s12), 0 + tempsum.Sum())); }
/** * Add an edge to the polygon or polyline. * <p> * @param azi azimuth at current point (degrees). * @param s distance from current point to next point (meters). * <p> * <i>azi</i> should be in the range [−540°, 540°). This does * nothing if no points have been added yet. Use PolygonArea.CurrentPoint to * determine the position of the new vertex. **********************************************************************/ public void AddEdge(double azi, double s) { if (_num > 0) { // Do nothing if _num is zero GeodesicData g = _earth.Direct(_lat1, _lon1, azi, s, _mask); _perimetersum.Add(g.s12); if (!_polyline) { _areasum.Add(g.S12); _crossings += transit(_lon1, g.lon2); } _lat1 = g.lat2; _lon1 = g.lon2; ++_num; } }
/** * Forward projection, from geographic to gnomonic. * <p> * @param lat0 latitude of center point of projection (degrees). * @param lon0 longitude of center point of projection (degrees). * @param lat latitude of point (degrees). * @param lon longitude of point (degrees). * @return {@link GnomonicData} object with the following fields: * <i>lat0</i>, <i>lon0</i>, <i>lat</i>, <i>lon</i>, <i>x</i>, <i>y</i>, * <i>azi</i>, <i>rk</i>. * <p> * <i>lat0</i> and <i>lat</i> should be in the range [−90°, * 90°] and <i>lon0</i> and <i>lon</i> should be in the range * [−540°, 540°). The scale of the projection is * 1/<i>rk<sup>2</sup></i> in the "radial" direction, <i>azi</i> clockwise * from true north, and is 1/<i>rk</i> in the direction perpendicular to * this. If the point lies "over the horizon", i.e., if <i>rk</i> ≤ 0, * then NaNs are returned for <i>x</i> and <i>y</i> (the correct values are * returned for <i>azi</i> and <i>rk</i>). A call to Forward followed by a * call to Reverse will return the original (<i>lat</i>, <i>lon</i>) (to * within roundoff) provided the point in not over the horizon. */ public GnomonicData Forward(double lat0, double lon0, double lat, double lon) { GeodesicData inv = Earth.Inverse( lat0, lon0, lat, lon, GeodesicMask.AZIMUTH | GeodesicMask.GEODESICSCALE | GeodesicMask.REDUCEDLENGTH); if (inv.M12 > 0) { var rho = inv.m12 / inv.M12; var p = GeoMath.Sincosd(inv.azi1); var x = rho * p.First; var y = rho * p.Second; return(new GnomonicData(lat0, lon0, lat, lon, x, y, inv.azi2, inv.M12)); } else { return(new GnomonicData(lat0, lon0, lat, lon, Double.NaN, Double.NaN, inv.azi2, inv.M12)); } }
/** * Add a point to the polygon or polyline. * <p> * @param lat the latitude of the point (degrees). * @param lon the latitude of the point (degrees). * <p> * <i>lat</i> should be in the range [−90°, 90°] and <i>lon</i> * should be in the range [−540°, 540°). **********************************************************************/ public void AddPoint(double lat, double lon) { lon = GeoMath.AngNormalize(lon); if (_num == 0) { _lat0 = _lat1 = lat; _lon0 = _lon1 = lon; } else { GeodesicData g = _earth.Inverse(_lat1, _lon1, lat, lon, _mask); _perimetersum.Add(g.s12); if (!_polyline) { _areasum.Add(g.S12); _crossings += transit(_lon1, lon); } _lat1 = lat; _lon1 = lon; } ++_num; }
/** * Return the results assuming a tentative final test point is added via an * azimuth and distance; however, the data for the test point is not saved. * This lets you report a running result for the Perimeter and Area as the * user moves the mouse cursor. Ordinary floating point arithmetic is used * to accumulate the data for the test point; thus the Area and Perimeter * returned are less accurate than if AddPoint and Compute are used. * <p> * @param azi azimuth at current point (degrees). * @param s distance from current point to final test point (meters). * @param reverse if true then clockwise (instead of counter-clockwise) * traversal counts as a positive Area. * @param sign if true then return a signed result for the Area if * the polygon is traversed in the "wrong" direction instead of returning * the Area for the rest of the earth. * @return PolygonResult(<i>Num</i>, <i>Perimeter</i>, <i>Area</i>) where * <i>Num</i> is the number of vertices, <i>Perimeter</i> is the Perimeter * of the polygon or the length of the polyline (meters), and <i>Area</i> * is the Area of the polygon (meters<sup>2</sup>) or Double.NaN of * <i>polyline</i> is true in the constructor. * <p> * <i>azi</i> should be in the range [−540°, 540°). **********************************************************************/ public PolygonResult TestEdge(double azi, double s, bool reverse, bool sign) { if (_num == 0) // we don't have a starting point! { return(new PolygonResult(0, Double.NaN, Double.NaN)); } int num = _num + 1; double perimeter = _perimetersum.Sum() + s; if (_polyline) { return(new PolygonResult(num, perimeter, Double.NaN)); } double tempsum = _areasum.Sum(); int crossings = _crossings; { double lat, lon, s12, S12, t; GeodesicData g = _earth.Direct(_lat1, _lon1, azi, false, s, _mask); tempsum += g.S12; crossings += transit(_lon1, g.lon2); g = _earth.Inverse(g.lat2, g.lon2, _lat0, _lon0, _mask); perimeter += g.s12; tempsum += g.S12; crossings += transit(g.lon2, _lon0); } if ((crossings & 1) != 0) { tempsum += (tempsum < 0 ? 1 : -1) * _area0 / 2; } // Area is with the clockwise sense. If !reverse convert to // counter-clockwise convention. if (!reverse) { tempsum *= -1; } // If sign put Area in (-area0/2, area0/2], else put Area in [0, area0) if (sign) { if (tempsum > _area0 / 2) { tempsum -= _area0; } else if (tempsum <= -_area0 / 2) { tempsum += _area0; } } else { if (tempsum >= _area0) { tempsum -= _area0; } else if (tempsum < 0) { tempsum += _area0; } } return(new PolygonResult(num, perimeter, 0 + tempsum)); }
/** * Return the results assuming a tentative final test point is added; * however, the data for the test point is not saved. This lets you report * a running result for the Perimeter and Area as the user moves the mouse * cursor. Ordinary floating point arithmetic is used to accumulate the * data for the test point; thus the Area and Perimeter returned are less * accurate than if AddPoint and Compute are used. * <p> * @param lat the latitude of the test point (degrees). * @param lon the longitude of the test point (degrees). * @param reverse if true then clockwise (instead of counter-clockwise) * traversal counts as a positive Area. * @param sign if true then return a signed result for the Area if * the polygon is traversed in the "wrong" direction instead of returning * the Area for the rest of the earth. * @return PolygonResult(<i>Num</i>, <i>Perimeter</i>, <i>Area</i>) where * <i>Num</i> is the number of vertices, <i>Perimeter</i> is the Perimeter * of the polygon or the length of the polyline (meters), and <i>Area</i> * is the Area of the polygon (meters<sup>2</sup>) or Double.NaN of * <i>polyline</i> is true in the constructor. * <p> * <i>lat</i> should be in the range [−90°, 90°] and <i>lon</i> * should be in the range [−540°, 540°). **********************************************************************/ public PolygonResult TestPoint(double lat, double lon, bool reverse, bool sign) { if (_num == 0) { return(new PolygonResult(1, 0, _polyline ? Double.NaN : 0)); } double perimeter = _perimetersum.Sum(); double tempsum = _polyline ? 0 : _areasum.Sum(); int crossings = _crossings; int num = _num + 1; for (int i = 0; i < (_polyline ? 1 : 2); ++i) { GeodesicData g = _earth.Inverse(i == 0 ? _lat1 : lat, i == 0 ? _lon1 : lon, i != 0 ? _lat0 : lat, i != 0 ? _lon0 : lon, _mask); perimeter += g.s12; if (!_polyline) { tempsum += g.S12; crossings += transit(i == 0 ? _lon1 : lon, i != 0 ? _lon0 : lon); } } if (_polyline) { return(new PolygonResult(num, perimeter, Double.NaN)); } if ((crossings & 1) != 0) { tempsum += (tempsum < 0 ? 1 : -1) * _area0 / 2; } // Area is with the clockwise sense. If !reverse convert to // counter-clockwise convention. if (!reverse) { tempsum *= -1; } // If sign put Area in (-area0/2, area0/2], else put Area in [0, area0) if (sign) { if (tempsum > _area0 / 2) { tempsum -= _area0; } else if (tempsum <= -_area0 / 2) { tempsum += _area0; } } else { if (tempsum >= _area0) { tempsum -= _area0; } else if (tempsum < 0) { tempsum += _area0; } } return(new PolygonResult(num, perimeter, 0 + tempsum)); }
/** * The general position function. {@link #Position(double, int) Position} * and {@link #ArcPosition(double, int) ArcPosition} are defined in terms of * this function. * <p> * @param arcmode bool flag determining the meaning of the second * parameter; if arcmode is false, then the GeodesicLine object must have * been constructed with <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN}. * @param s12_a12 if <i>arcmode</i> 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 outmask a bitor'ed combination of {@link GeodesicMask} values * specifying which results should be returned. * @return a {@link GeodesicData} object with the requested results. * <p> * The {@link GeodesicMask} values possible for <i>outmask</i> are * <ul> * <li> * <i>outmask</i> |= {@link GeodesicMask#LATITUDE} for the latitude * <i>lat2</i>; * <li> * <i>outmask</i> |= {@link GeodesicMask#LONGITUDE} for the latitude * <i>lon2</i>; * <li> * <i>outmask</i> |= {@link GeodesicMask#AZIMUTH} for the latitude * <i>azi2</i>; * <li> * <i>outmask</i> |= {@link GeodesicMask#DISTANCE} for the distance * <i>s12</i>; * <li> * <i>outmask</i> |= {@link GeodesicMask#REDUCEDLENGTH} for the reduced * length <i>m12</i>; * <li> * <i>outmask</i> |= {@link GeodesicMask#GEODESICSCALE} for the geodesic * scales <i>M12</i> and <i>M21</i>; * <li> * <i>outmask</i> |= {@link GeodesicMask#ALL} for all of the above; * <li> * <i>outmask</i> |= {@link GeodesicMask#LONG_UNROLL} to unroll <i>lon2</i> * (instead of reducing it to the range [−180°, 180°]). * </ul> * <p> * Requesting a value which the GeodesicLine object is not capable of * computing is not an error; Double.NaN is returned instead. **********************************************************************/ public GeodesicData Position(bool arcmode, double s12_a12, int outmask) { outmask &= _caps & GeodesicMask.OUT_MASK; GeodesicData r = GeodesicData.NaN; if (!(Init && (arcmode || (_caps & (GeodesicMask.OUT_MASK & GeodesicMask.DISTANCE_IN)) != 0))) { // Uninitialized or impossible distance calculation requested return(r); } r.lat1 = _lat1; r.azi1 = _azi1; r.lon1 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) ? _lon1 : GeoMath.AngNormalize(_lon1); // Avoid warning about uninitialized B12. double sig12, ssig12, csig12, B12 = 0, AB1 = 0; if (arcmode) { // Interpret s12_a12 as spherical arc length r.a12 = s12_a12; sig12 = GeoMath.ToRadians(s12_a12); { Pair p = GeoMath.Sincosd(s12_a12); ssig12 = p.First; csig12 = p.Second; } } else { // Interpret s12_a12 as distance r.s12 = s12_a12; 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 ssig2_ = _ssig1 * csig12 + _csig1 * ssig12, csig2_ = _csig1 * csig12 - _ssig1 * ssig12; B12 = Geodesic.SinCosSeries(true, ssig2_, csig2_, _C1a); double serr = (1 + _A1m1) * (sig12 + (B12 - _B11)) - s12_a12 / _b; sig12 = sig12 - serr / Math.Sqrt(1 + _k2 * GeoMath.Sq(ssig2_)); ssig12 = Math.Sin(sig12); csig12 = Math.Cos(sig12); // Update B12 below } r.a12 = GeoMath.ToDegrees(sig12); } 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.Sq(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 && arcmode) { r.s12 = _b * ((1 + _A1m1) * sig12 + AB1); } 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 or west 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 = GeoMath.ToDegrees(lam12); r.lon2 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) ? _lon1 + lon12 : GeoMath.AngNormalize(r.lon1 + GeoMath.AngNormalize(lon12)); } if ((outmask & GeodesicMask.LATITUDE) != 0) { r.lat2 = GeoMath.Atan2d(sbet2, _f1 * cbet2); } if ((outmask & GeodesicMask.AZIMUTH) != 0) { r.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. r.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); r.M12 = csig12 + (t * ssig2 - csig2 * J12) * _ssig1 / _dn1; r.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 atan2 so no need to normalize salp12 = salp2 * _calp1 - calp2 * _salp1; calp12 = calp2 * _calp1 + salp2 * _salp1; } 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.Sq(_salp0) + GeoMath.Sq(_calp0) * _csig1 * csig2; } r.S12 = _c2 * Math.Atan2(salp12, calp12) + _A4 * (B42 - _B41); } return(r); }
/** * The general position function. {@link #Position(double, int) Position} * and {@link #ArcPosition(double, int) ArcPosition} are defined in terms of * this function. * <p> * @param arcmode bool flag determining the meaning of the Second * parameter; if arcmode is false, then the GeodesicLine object must have * been constructed with <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN}. * @param s12_a12 if <i>arcmode</i> 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 outmask a bitor'ed combination of {@link GeodesicMask} values * specifying which results should be returned. * @return a {@link GeodesicData} object with the requested results. * <p> * The {@link GeodesicMask} values possible for <i>outmask</i> are * <ul> * <li> * <i>outmask</i> |= GeodesicMask.LATITUDE for the latitude <i>lat2</i>. * <li> * <i>outmask</i> |= GeodesicMask.LONGITUDE for the latitude <i>lon2</i>. * <li> * <i>outmask</i> |= GeodesicMask.AZIMUTH for the latitude <i>azi2</i>. * <li> * <i>outmask</i> |= GeodesicMask.DISTANCE for the distance <i>s12</i>. * <li> * <i>outmask</i> |= GeodesicMask.REDUCEDLENGTH for the reduced length * <i>m12</i>. * <li> * <i>outmask</i> |= GeodesicMask.GEODESICSCALE for the geodesic scales * <i>M12</i> and <i>M21</i>. * <li> * <i>outmask</i> |= GeodesicMask.AREA for the Area <i>S12</i>. * </ul> * <p> * Requesting a value which the GeodesicLine object is not capable of * computing is not an error; Double.NaN is returned instead. **********************************************************************/ public GeodesicData Position(bool arcmode, double s12_a12, int outmask) { outmask &= _caps & GeodesicMask.OUT_ALL; GeodesicData r = new GeodesicData(); if (!(Init() && (arcmode || (_caps & GeodesicMask.DISTANCE_IN & GeodesicMask.OUT_ALL) != 0))) { // Uninitialized or impossible distance calculation requested return(r); } r.lat1 = _lat1; r.lon1 = _lon1; r.azi1 = _azi1; // Avoid warning about uninitialized B12. double sig12, ssig12, csig12, B12 = 0, AB1 = 0; if (arcmode) { // Interpret s12_a12 as spherical arc length r.a12 = s12_a12; sig12 = s12_a12 * GeoMath.Degree; double s12a = Math.Abs(s12_a12); s12a -= 180 * Math.Floor(s12a / 180); ssig12 = s12a == 0 ? 0 : Math.Sin(sig12); csig12 = s12a == 90 ? 0 : Math.Cos(sig12); } else { // Interpret s12_a12 as distance r.s12 = s12_a12; 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); r.a12 = sig12 / GeoMath.Degree; 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 ssig2 = _ssig1 * csig12 + _csig1 * ssig12, csig2 = _csig1 * csig12 - _ssig1 * ssig12; B12 = Geodesic.SinCosSeries(true, ssig2, csig2, _C1a); double serr = (1 + _A1m1) * (sig12 + (B12 - _B11)) - s12_a12 / _b; sig12 = sig12 - serr / Math.Sqrt(1 + _k2 * GeoMath.Sq(ssig2)); ssig12 = Math.Sin(sig12); csig12 = Math.Cos(sig12); // Update B12 below } } double omg12, lam12, lon12; double ssig3, csig3, sbet3, cbet3, somg3, comg3, salp3, calp3; // sig2 = sig1 + sig12 ssig3 = _ssig1 * csig12 + _csig1 * ssig12; csig3 = _csig1 * csig12 - _ssig1 * ssig12; double dn2 = Math.Sqrt(1 + _k2 * GeoMath.Sq(ssig3)); if ((outmask & (GeodesicMask.DISTANCE | GeodesicMask.REDUCEDLENGTH | GeodesicMask.GEODESICSCALE)) != 0) { if (arcmode || Math.Abs(_f) > 0.01) { B12 = Geodesic.SinCosSeries(true, ssig3, csig3, _C1a); } AB1 = (1 + _A1m1) * (B12 - _B11); } // sin(bet2) = cos(alp0) * sin(sig2) sbet3 = _calp0 * ssig3; // Alt: cbet3 = Hypot(csig3, salp0 * ssig3); cbet3 = GeoMath.Hypot(_salp0, _calp0 * csig3); if (cbet3 == 0) { // I.e., salp0 = 0, csig3 = 0. Break the degeneracy in this case cbet3 = csig3 = Geodesic.tiny_; } // tan(omg2) = sin(alp0) * tan(sig2) somg3 = _salp0 * ssig3; comg3 = csig3; // No need to normalize // tan(alp0) = cos(sig2)*tan(alp2) salp3 = _salp0; calp3 = _calp0 * csig3; // No need to normalize // omg12 = omg2 - omg1 omg12 = Math.Atan2(somg3 * _comg1 - comg3 * _somg1, comg3 * _comg1 + somg3 * _somg1); if ((outmask & GeodesicMask.DISTANCE) != 0 && arcmode) { r.s12 = _b * ((1 + _A1m1) * sig12 + AB1); } if ((outmask & GeodesicMask.LONGITUDE) != 0) { lam12 = omg12 + _A3c * (sig12 + (Geodesic.SinCosSeries(true, ssig3, csig3, _C3a) - _B31)); lon12 = lam12 / GeoMath.Degree; // Use GeoMath.AngNormalize2 because longitude might have wrapped // multiple times. lon12 = GeoMath.AngNormalize2(lon12); r.lon2 = GeoMath.AngNormalize(_lon1 + lon12); } if ((outmask & GeodesicMask.LATITUDE) != 0) { r.lat2 = Math.Atan2(sbet3, _f1 * cbet3) / GeoMath.Degree; } if ((outmask & GeodesicMask.AZIMUTH) != 0) { // minus signs give range [-180, 180). 0- converts -0 to +0. r.azi2 = 0 - Math.Atan2(-salp3, calp3) / GeoMath.Degree; } if ((outmask & (GeodesicMask.REDUCEDLENGTH | GeodesicMask.GEODESICSCALE)) != 0) { double B22 = Geodesic.SinCosSeries(true, ssig3, csig3, _C2a), AB2 = (1 + _A2m1) * (B22 - _B21), J12 = (_A1m1 - _A2m1) * sig12 + (AB1 - AB2); if ((outmask & GeodesicMask.REDUCEDLENGTH) != 0) { // Add parens around (_csig1 * ssig3) and (_ssig1 * csig3) to ensure // accurate cancellation in the case of coincident points. r.m12 = _b * ((dn2 * (_csig1 * ssig3) - _dn1 * (_ssig1 * csig3)) - _csig1 * csig3 * J12); } if ((outmask & GeodesicMask.GEODESICSCALE) != 0) { double t = _k2 * (ssig3 - _ssig1) * (ssig3 + _ssig1) / (_dn1 + dn2); r.M12 = csig12 + (t * ssig3 - csig3 * J12) * _ssig1 / _dn1; r.M21 = csig12 - (t * _ssig1 - _csig1 * J12) * ssig3 / dn2; } } if ((outmask & GeodesicMask.AREA) != 0) { double B42 = Geodesic.SinCosSeries(false, ssig3, csig3, _C4a); double salp12, calp12; if (_calp0 == 0 || _salp0 == 0) { // alp12 = alp2 - alp1, used in atan2 so no need to normalized salp12 = salp3 * _calp1 - calp3 * _salp1; calp12 = calp3 * _calp1 + salp3 * _salp1; // The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz // salp12 = -0 and alp12 = -180. However this depends on the sign // being attached to 0 correctly. The following ensures the correct // behavior. if (salp12 == 0 && calp12 < 0) { salp12 = Geodesic.tiny_ * _calp1; calp12 = -1; } } else { // tan(alp) = tan(alp0) * sec(sig) // tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) // = calp0 * salp0 * (csig1-csig3) / (salp0^2 + calp0^2 * csig1*csig3) // If csig12 > 0, write // csig1 - csig3 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) // else // csig1 - csig3 = 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.Sq(_salp0) + GeoMath.Sq(_calp0) * _csig1 * csig3; } r.S12 = _c2 * Math.Atan2(salp12, calp12) + _A4 * (B42 - _B41); } return(r); }