/** * Implementation of the net.sf.geographiclib.GeodesicLine class * * Copyright (c) Charles Karney (2013-2019) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ package net.sf.geographiclib; /** * A geodesic line. *

* GeodesicLine facilitates the determination of a series of points on a single * geodesic. The starting point (lat1, lon1) and the azimuth * azi1 are specified in the constructor; alternatively, the {@link * Geodesic#Line Geodesic.Line} method can be used to create a GeodesicLine. * {@link #Position Position} returns the location of point 2 a distance * s12 along the geodesic. Alternatively {@link #ArcPosition * ArcPosition} gives the position of point 2 an arc length a12 along * the geodesic. *

* You can register the position of a reference point 3 a distance (arc * length), s13 (a13) along the geodesic with the * {@link #SetDistance SetDistance} ({@link #SetArc SetArc}) functions. Points * a fractional distance along the line can be found by providing, for example, * 0.5 * {@link #Distance} as an argument to {@link #Position Position}. The * {@link Geodesic#InverseLine Geodesic.InverseLine} or * {@link Geodesic#DirectLine Geodesic.DirectLine} methods return GeodesicLine * objects with point 3 set to the point 2 of the corresponding geodesic * problem. GeodesicLine objects created with the public constructor or with * {@link Geodesic#Line Geodesic.Line} have s13 and a13 set to * NaNs. *

* The calculations are accurate to better than 15 nm (15 nanometers). See * Sec. 9 of * arXiv:1102.1215v1 for * details. The algorithms used by this class are based on series expansions * using the flattening f as a small parameter. These are only accurate * for |f| < 0.02; however reasonably accurate results will be * obtained for |f| < 0.2. *

* The algorithms are described in *

*

* Here's an example of using this class *

 * {@code
 * import net.sf.geographiclib.*;
 * public class GeodesicLineTest {
 *   public static void main(String[] args) {
 *     // Print waypoints between JFK and SIN
 *     Geodesic geod = Geodesic.WGS84;
 *     double
 *       lat1 = 40.640, lon1 = -73.779, // JFK
 *       lat2 =  1.359, lon2 = 103.989; // SIN
 *     GeodesicLine line = geod.InverseLine(lat1, lon1, lat2, lon2,
 *                                          GeodesicMask.DISTANCE_IN |
 *                                          GeodesicMask.LATITUDE |
 *                                          GeodesicMask.LONGITUDE);
 *     double ds0 = 500e3;     // Nominal distance between points = 500 km
 *     // The number of intervals
 *     int num = (int)(Math.ceil(line.Distance() / ds0));
 *     {
 *       // Use intervals of equal length
 *       double ds = line.Distance() / num;
 *       for (int i = 0; i <= num; ++i) {
 *         GeodesicData g = line.Position(i * ds,
 *                                        GeodesicMask.LATITUDE |
 *                                        GeodesicMask.LONGITUDE);
 *         System.out.println(i + " " + g.lat2 + " " + g.lon2);
 *       }
 *     }
 *     {
 *       // Slightly faster, use intervals of equal arc length
 *       double da = line.Arc() / num;
 *       for (int i = 0; i <= num; ++i) {
 *         GeodesicData g = line.ArcPosition(i * da,
 *                                           GeodesicMask.LATITUDE |
 *                                           GeodesicMask.LONGITUDE);
 *         System.out.println(i + " " + g.lat2 + " " + g.lon2);
 *       }
 *     }
 *   }
 * }}
**********************************************************************/ public class GeodesicLine { private static final int nC1_ = Geodesic.nC1_; private static final int nC1p_ = Geodesic.nC1p_; private static final int nC2_ = Geodesic.nC2_; private static final int nC3_ = Geodesic.nC3_; private static final int nC4_ = Geodesic.nC4_; private double _lat1, _lon1, _azi1; private double _a, _f, _b, _c2, _f1, _salp0, _calp0, _k2, _salp1, _calp1, _ssig1, _csig1, _dn1, _stau1, _ctau1, _somg1, _comg1, _A1m1, _A2m1, _A3c, _B11, _B21, _B31, _A4, _B41; private double _a13, _s13; // index zero elements of _C1a, _C1pa, _C2a, _C3a are unused private double _C1a[], _C1pa[], _C2a[], _C3a[], _C4a[]; // all the elements of _C4a are used private int _caps; /** * Constructor for a geodesic line staring at latitude lat1, longitude * lon1, and azimuth azi1 (all in degrees). *

* @param g A {@link Geodesic} object used to compute the necessary * information about the GeodesicLine. * @param lat1 latitude of point 1 (degrees). * @param lon1 longitude of point 1 (degrees). * @param azi1 azimuth at point 1 (degrees). *

* lat1 should be in the range [−90°, 90°]. *

* If the point is at a pole, the azimuth is defined by keeping lon1 * fixed, writing lat1 = ±(90° − ε), and * taking the limit ε → 0+. **********************************************************************/ public GeodesicLine(Geodesic g, double lat1, double lon1, double azi1) { this(g, lat1, lon1, azi1, GeodesicMask.ALL); } /** * Constructor for a geodesic line staring at latitude lat1, longitude * lon1, and azimuth azi1 (all in degrees) with a subset of the * capabilities included. *

* @param g A {@link Geodesic} object used to compute the necessary * information about the GeodesicLine. * @param lat1 latitude of point 1 (degrees). * @param lon1 longitude of point 1 (degrees). * @param azi1 azimuth at point 1 (degrees). * @param caps bitor'ed combination of {@link GeodesicMask} values * specifying the capabilities the GeodesicLine object should possess, * i.e., which quantities can be returned in calls to {@link #Position * Position}. *

* The {@link GeodesicMask} values are *

**********************************************************************/ public GeodesicLine(Geodesic g, double lat1, double lon1, double azi1, int caps) { azi1 = GeoMath.AngNormalize(azi1); double salp1, calp1; // Guard against underflow in salp0 { Pair p = GeoMath.sincosd(GeoMath.AngRound(azi1)); salp1 = p.first; calp1 = p.second; } LineInit(g, lat1, lon1, azi1, salp1, calp1, caps); } private void LineInit(Geodesic g, double lat1, double lon1, double azi1, double salp1, double calp1, int caps) { _a = g._a; _f = g._f; _b = g._b; _c2 = g._c2; _f1 = g._f1; // Always allow latitude and azimuth and unrolling the longitude _caps = caps | GeodesicMask.LATITUDE | GeodesicMask.AZIMUTH | GeodesicMask.LONG_UNROLL; _lat1 = GeoMath.LatFix(lat1); _lon1 = lon1; _azi1 = azi1; _salp1 = salp1; _calp1 = calp1; double cbet1, sbet1; { Pair p = GeoMath.sincosd(GeoMath.AngRound(_lat1)); sbet1 = _f1 * p.first; cbet1 = p.second; } // Ensure cbet1 = +epsilon at poles { Pair p = GeoMath.norm(sbet1, cbet1); sbet1 = p.first; cbet1 = Math.max(Geodesic.tiny_, p.second); } _dn1 = Math.sqrt(1 + g._ep2 * GeoMath.sq(sbet1)); // Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), _salp0 = _salp1 * cbet1; // alp0 in [0, pi/2 - |bet1|] // Alt: calp0 = Math.hypot(sbet1, calp1 * cbet1). The following // is slightly better (consider the case salp1 = 0). _calp0 = Math.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 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; { Pair p = GeoMath.norm(_ssig1, _csig1); _ssig1 = p.first; _csig1 = p.second; } // sig1 in (-pi, pi] // GeoMath.norm(_somg1, _comg1); -- don't need to normalize! _k2 = GeoMath.sq(_calp0) * g._ep2; double eps = _k2 / (2 * (1 + Math.sqrt(1 + _k2)) + _k2); if ((_caps & GeodesicMask.CAP_C1) != 0) { _A1m1 = Geodesic.A1m1f(eps); _C1a = new double[nC1_ + 1]; Geodesic.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]; Geodesic.C1pf(eps, _C1pa); } if ((_caps & GeodesicMask.CAP_C2) != 0) { _C2a = new double[nC2_ + 1]; _A2m1 = Geodesic.A2m1f(eps); Geodesic.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.sq(_a) * _calp0 * _salp0 * g._e2; _B41 = Geodesic.SinCosSeries(false, _ssig1, _csig1, _C4a); } } protected GeodesicLine(Geodesic g, double lat1, double lon1, double azi1, double salp1, double calp1, int caps, boolean arcmode, double s13_a13) { LineInit(g, lat1, lon1, azi1, salp1, calp1, caps); GenSetDistance(arcmode, s13_a13); } /** * A default constructor. If GeodesicLine.Position is called on the * resulting object, it returns immediately (without doing any calculations). * The object can be set with a call to {@link Geodesic.Line}. Use {@link * Init()} to test whether object is still in this uninitialized state. * (This constructor was useful in C++, e.g., to allow vectors of * GeodesicLine objects. It may not be needed in Java, so make it private.) **********************************************************************/ private GeodesicLine() { _caps = 0; } /** * Compute the position of point 2 which is a distance s12 (meters) * from point 1. *

* @param s12 distance from point 1 to point 2 (meters); it can be * negative. * @return a {@link GeodesicData} object with the following fields: * lat1, lon1, azi1, lat2, lon2, * azi2, s12, a12. Some of these results may be * missing if the GeodesicLine did not include the relevant capability. *

* The values of lon2 and azi2 returned are in the range * [−180°, 180°]. *

* The GeodesicLine object must have been constructed with caps * |= {@link GeodesicMask#DISTANCE_IN}; otherwise no parameters are set. **********************************************************************/ public GeodesicData Position(double s12) { return Position(false, s12, GeodesicMask.STANDARD); } /** * Compute the position of point 2 which is a distance s12 (meters) * from point 1 and with a subset of the geodesic results returned. *

* @param s12 distance from point 1 to point 2 (meters); 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 including the requested results. *

* The GeodesicLine object must have been constructed with caps * |= {@link GeodesicMask#DISTANCE_IN}; otherwise no parameters are set. * Requesting a value which the GeodesicLine object is not capable of * computing is not an error (no parameters will be set). The value of * lon2 returned is normally in the range [−180°, 180°]; * however if the outmask includes the * {@link GeodesicMask#LONG_UNROLL} flag, the longitude is "unrolled" so that * the quantity lon2lon1 indicates how many times and * in what sense the geodesic encircles the ellipsoid. **********************************************************************/ public GeodesicData Position(double s12, int outmask) { return Position(false, s12, outmask); } /** * Compute the position of point 2 which is an arc length a12 * (degrees) from point 1. *

* @param a12 arc length from point 1 to point 2 (degrees); it can * be negative. * @return a {@link GeodesicData} object with the following fields: * lat1, lon1, azi1, lat2, lon2, * azi2, s12, a12. Some of these results may be * missing if the GeodesicLine did not include the relevant capability. *

* The values of lon2 and azi2 returned are in the range * [−180°, 180°]. *

* The GeodesicLine object must have been constructed with caps * |= {@link GeodesicMask#DISTANCE_IN}; otherwise no parameters are set. **********************************************************************/ public GeodesicData ArcPosition(double a12) { return Position(true, a12, GeodesicMask.STANDARD); } /** * Compute the position of point 2 which is an arc length a12 * (degrees) from point 1 and with a subset of the geodesic results returned. *

* @param a12 arc length from point 1 to 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 giving lat1, lon2, * azi2, and a12. *

* Requesting a value which the GeodesicLine object is not capable of * computing is not an error (no parameters will be set). The value of * lon2 returned is in the range [−180°, 180°], unless * the outmask includes the {@link GeodesicMask#LONG_UNROLL} flag. **********************************************************************/ public GeodesicData ArcPosition(double a12, int outmask) { return Position(true, a12, outmask); } /** * The general position function. {@link #Position(double, int) Position} * and {@link #ArcPosition(double, int) ArcPosition} are defined in terms of * this function. *

* @param arcmode boolean flag determining the meaning of the second * parameter; if arcmode is false, then the GeodesicLine object must have * been constructed with caps |= {@link GeodesicMask#DISTANCE_IN}. * @param s12_a12 if 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 outmask a bitor'ed combination of {@link GeodesicMask} values * specifying which results should be returned. * @return a {@link GeodesicData} object with the requested results. *

* The {@link GeodesicMask} values possible for outmask are *

*

* Requesting a value which the GeodesicLine object is not capable of * computing is not an error; Double.NaN is returned instead. **********************************************************************/ public GeodesicData Position(boolean arcmode, double s12_a12, int outmask) { outmask &= _caps & GeodesicMask.OUT_MASK; GeodesicData r = new GeodesicData(); 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 = Math.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 = Math.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 = Math.hypot(csig2, salp0 * ssig2); cbet2 = Math.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 = Math.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 = Math.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; } /** * Specify position of point 3 in terms of distance. * * @param s13 the distance from point 1 to point 3 (meters); it * can be negative. * * This is only useful if the GeodesicLine object has been constructed * with caps |= {@link GeodesicMask#DISTANCE_IN}. **********************************************************************/ public void SetDistance(double s13) { _s13 = s13; GeodesicData g = Position(false, _s13, 0); _a13 = g.a12; } /** * Specify position of point 3 in terms of arc length. * * @param a13 the arc length from point 1 to point 3 (degrees); it * can be negative. * * The distance s13 is only set if the GeodesicLine object has been * constructed with caps |= {@link GeodesicMask#DISTANCE}. **********************************************************************/ void SetArc(double a13) { _a13 = a13; GeodesicData g = Position(true, _a13, GeodesicMask.DISTANCE); _s13 = g.s12; } /** * Specify position of point 3 in terms of either distance or arc length. * * @param arcmode boolean flag determining the meaning of the second * parameter; if arcmode is false, then the GeodesicLine object must * have been constructed with caps |= * {@link GeodesicMask#DISTANCE_IN}. * @param s13_a13 if arcmode is false, this is the distance from * point 1 to point 3 (meters); otherwise it is the arc length from * point 1 to point 3 (degrees); it can be negative. **********************************************************************/ public void GenSetDistance(boolean arcmode, double s13_a13) { if (arcmode) SetArc(s13_a13); else SetDistance(s13_a13); } /** * @return true if the object has been initialized. **********************************************************************/ private boolean Init() { return _caps != 0; } /** * @return lat1 the latitude of point 1 (degrees). **********************************************************************/ public double Latitude() { return Init() ? _lat1 : Double.NaN; } /** * @return lon1 the longitude of point 1 (degrees). **********************************************************************/ public double Longitude() { return Init() ? _lon1 : Double.NaN; } /** * @return azi1 the azimuth (degrees) of the geodesic line at point 1. **********************************************************************/ public double Azimuth() { return Init() ? _azi1 : Double.NaN; } /** * @return pair of sine and cosine of azi1 the azimuth (degrees) of * the geodesic line at point 1. **********************************************************************/ public Pair AzimuthCosines() { return new Pair(Init() ? _salp1 : Double.NaN, Init() ? _calp1 : Double.NaN); } /** * @return azi0 the azimuth (degrees) of the geodesic line as it * crosses the equator in a northward direction. **********************************************************************/ public double EquatorialAzimuth() { return Init() ? GeoMath.atan2d(_salp0, _calp0) : Double.NaN; } /** * @return pair of sine and cosine of azi0 the azimuth of the geodesic * line as it crosses the equator in a northward direction. **********************************************************************/ public Pair EquatorialAzimuthCosines() { return new Pair(Init() ? _salp0 : Double.NaN, Init() ? _calp0 : Double.NaN); } /** * @return a1 the arc length (degrees) between the northward * equatorial crossing and point 1. **********************************************************************/ public double EquatorialArc() { return Init() ? GeoMath.atan2d(_ssig1, _csig1) : Double.NaN; } /** * @return a the equatorial radius of the ellipsoid (meters). This is * the value inherited from the Geodesic object used in the constructor. **********************************************************************/ public double EquatorialRadius() { return Init() ? _a : Double.NaN; } /** * @return f the flattening of the ellipsoid. This is the value * inherited from the Geodesic object used in the constructor. **********************************************************************/ public double Flattening() { return Init() ? _f : Double.NaN; } /** * @return caps the computational capabilities that this object was * constructed with. LATITUDE and AZIMUTH are always included. **********************************************************************/ public int Capabilities() { return _caps; } /** * @param testcaps a set of bitor'ed {@link GeodesicMask} values. * @return true if the GeodesicLine object has all these capabilities. **********************************************************************/ public boolean Capabilities(int testcaps) { testcaps &= GeodesicMask.OUT_ALL; return (_caps & testcaps) == testcaps; } /** * The distance or arc length to point 3. * * @param arcmode boolean flag determining the meaning of returned * value. * @return s13 if arcmode is false; a13 if * arcmode is true. **********************************************************************/ public double GenDistance(boolean arcmode) { return Init() ? (arcmode ? _a13 : _s13) : Double.NaN; } /** * @return s13, the distance to point 3 (meters). **********************************************************************/ public double Distance() { return GenDistance(false); } /** * @return a13, the arc length to point 3 (degrees). **********************************************************************/ public double Arc() { return GenDistance(true); } /** * @deprecated An old name for {@link #EquatorialRadius()}. * @return a the equatorial radius of the ellipsoid (meters). **********************************************************************/ // @Deprecated public double MajorRadius() { return EquatorialRadius(); } }