View Javadoc
1   /*
2    * Copyright (C) 2018 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.navigation.geodesic;
17  
18  /**
19   * Geodesic calculations.
20   * The shortest path between two points on an ellipsoid at (<i>lat1</i>, <i>lon1</i>) and
21   * (<i>lat2</i>, <i>lon2</i>) is called the geodesic. Its length is <i>s12</i> and the geodesic
22   * from point 1 to point 2 has azimuths <i>azi1</i> and <i>azi2</i> at the two end points. (The
23   * azimuth is the heading measured clockwise from north. <i>azi2</i> is the "forward" azimuth, i.e.,
24   * the heading that takes you beyond point 2 not back to point 1.).
25   * Given <i>lat1</i>, <i>lon1</i>, <i>azi1</i>, and <i>s12</i>, we can determine <i>lat2</i>,
26   * <i>lon2</i>, and <i>azi2</i>. This is the <i>direct</i> geodesic problem and its solution is given
27   * by th function {@link #direct}. (If <i>s12</i> is sufficiently large that the geodesic wraps
28   * more than halfway around the earth, there will be another geodesic between the points with
29   * a smaller <i>s12</i>.)
30   * Given <i>lat1</i>, <i>lon1</i>, <i>lat2</i>, and <i>lon2</i>, we can determine <i>azi1</i>,
31   * <i>azi2</i>, and <i>s12</i>. This is the <i>inverse</i> geodesic problem, whose solution is
32   * given by {@link #inverse}. Usually, the solution to the inverse problem is unique. In cases where
33   * there are multiple solutions (all with the same <i>s12</i>, of course), all the solutions can be
34   * easily generated once a particular solution is provided.
35   * The standard way of specifying the direct problem is to specify the distance <i>s12</i> to the
36   * second point. However, it is sometimes useful instead to specify the arc length <i>a12</i> (in
37   * degrees) on the auxiliary sphere. This is a mathematical construct used in solving the geodesic
38   * problems. The solution of the direct problem in this form is provided by {@link #arcDirect}. An
39   * arc length in excess of 180&deg; indicates that the geodesic is not the shortest path. In addition,
40   * the arc length between an equatorial crossing and the next extremum of latitude for a geodesic is
41   * 90&deg;.
42   * This class can also calculate several other quantities related to geodesics. These are:
43   * <ul>
44   *     <li>
45   *         <i>reduced length</i>. If we fix the first point and increase <i>azi1</i> by <i>dazi1</i>
46   *         (radians), the second point is displaced <i>m12</i> <i>dazi1</i> in the direction
47   *         <i>azi2</i> + 90&deg;. The quantity <i>m12</i> is called the "reduced length" and is
48   *         symmetric under interchange of the two points. On a curved surface the reduced length
49   *         obeys a symmetry relation, <i>m12</i> + <i>m21</i> = 0. On a flat surface, we have
50   *         <i>m12</i> = <i>s12</i>. The ratio <i>s12</i>/<i>m12</i> gives the azimuthal scale for
51   *         an azimuthal equidistant projection.
52   *     </li>
53   *     <li>
54   *         <i>geodesic scale</i>. Consider a reference geodesic and a second geodesic parallel to this
55   *         one at point 1 and separated by a small distance <i>dt</i>. The separation of the two
56   *         geodesics at point 2 is <i>M12</i><i>dt</i> where <i>M12</i> is called the "geodesic scale".
57   *         <i>M21</i> is defined similarly (with the geodesics being parallel at point 2). On a flat
58   *         surface, we have <i>M12</i> = <i>M21</i> = 1. The quantity 1/<i>M12</i> gives the scale
59   *         of the Cassini-Soldner projection.
60   *     </li>
61   *     <li>
62   *         <i>area</i>. The area between the geodesic from point 1 to point 2 and the equation is
63   *         represented by <i>S12</i>; it is the area, measured counter-clockwise, of the geodesic
64   *         quadrilateral with corners (<i>lat1</i>, <i>lon1</i>), (0, <i>lon1</i>), (0, <i>lon2</i>), and
65   *         (<i>lat2</i>, <i>lon2</i>). It can be used to compute the area of any single geodesic
66   *         polygon.
67   *     </li>
68   * </ul>
69   * The quantities <i>m12</i>, <i>M12</i>, <i>M21</i> which all specify the behavior of nearby geodesics
70   * obey addition rules. If points 1, 2, and 3 all lie on a single geodesic, then the following rules hold:
71   * <ul>
72   *     <li>
73   *         <i>s13</i> = <i>s12</i> + <i>s23</i>
74   *     </li>
75   *     <li>
76   *         <i>a13</i> = <i>a12</i> + <i>a23</i>
77   *     </li>
78   *     <li>
79   *         <i>S13</i> = <i>S12</i> + <i>S23</i>
80   *     </li>
81   *     <li>
82   *         <i>m13</i> = <i>m12</i> <i>M23</i> + <i>m23</i> <i>M21</i>
83   *     </li>
84   *     <li>
85   *         <i>M13</i> = <i>M12</i> <i>M23</i> &minus; (1 &minus; <i>M12</i> <i>M21</i>) <i>m23</i> / <i>m12</i>
86   *     </li>
87   *     <li>
88   *         <i>M31</i> = <i>M32</i> <i>M21</i> &minus; (1 &minus; <i>M23</i> <i>M32</i>) <i>m12</i> / <i>m23</i>
89   *     </li>
90   * </ul>
91   * The results of the geodesic calculations are bundled up into a {@link GeodesicData} object which includes the
92   * input parameters and all the computed results, i.e. <i>lat1</i>, <i>lon1</i>, <i>azi1</i>, <i>lat2</i>,
93   * <i>lon2</i>, <i>azi2</i>, <i>s12</i>, <i>a12</i>, <i>m12</i>, <i>M12</i>, <i>M21</i>, <i>S12</i>.
94   * The functions {@link #direct(double, double, double, double, int)},
95   * {@link #arcDirect(double, double, double, double, int)} and {@link #inverse(double, double, double, double, int)}
96   * include an optional final argument <i>outmask</i> which allows you to specify which results should be computed and
97   * returned. If you omit <i>outmask</i>, then the "standard" geodesic results are computed (latitudes,  longitudes,
98   * azimuths, and distance). <i>outmask</i> is bitor'ed combination of {@link GeodesicMask} values. For example, if
99   * you wish just to compute the distance between two points you would call, e.g.,
100  * <pre>
101  *     {@code
102  *     GeodesicData g = Geodesic.WGS84.inverse(lat1, lon1, lat2, lon2, GeodesicMask.DISTANCE);
103  *     }
104  * </pre>
105  * Additional functionality is provided by the {@link GeodesicLine} class, which allows a sequence of points along a
106  * geodesic to be computed.
107  * The shortest distance returned by the solution of the inverse problem is (obviously) uniquely defined. However, in
108  * a few special cases there are multiple azimuths which yield the same shortest distance. Here is a catalog of those
109  * cases:
110  * <ul>
111  *     <li>
112  *         <i>lat1</i> = &minus;<i>lat2</i> (with neither point at a pole). If
113  *         <i>azi1</i> = <i>azi2</i>, the geodesic is unique. Otherwise there are two geodesics and the second one is
114  *         obtained by setting [<i>azi1</i>, <i>azi2</i>] &rarr; [<i>azi2</i>, <i>azi1</i>], [<i>M12</i>, <i>M21</i>]
115  *         &rarr; [<i>M21</i>, <i>M12</i>], <i>S12</i> &rarr; &minus;<i>S12</i>.
116  *         (This occurs when the longitude difference is near &plusmn;180&deg; for oblate ellipsoids.)
117  *     </li>
118  *     <li>
119  *         <i>lon2</i> = <i>lon1</i> + 180&deg; (with neither point at pole). If <i>azi1</i> = 0&deg; or
120  *         &plusmn;180&deg;, the geodesic is unique. Otherwise there are two geodesics and the second one is obtained
121  *         by setting [<i>azi1</i>, <i>azi2</i>] &rarr; [&minus;<i>azi1</i>, &minus;<i>azi2</i>], <i>S12</i> &rarr;
122  *         &minus; <i>S12</i>. (This occurs when <i>lat2</i> is near &minus;<i>lat1</i> for prolate ellipsoids.)
123  *     </li>
124  *     <li>
125  *         Points 1 and 2 at opposite poles. There are infinitely many geodesics which can be generated by setting
126  *         [<i>azi1</i>, <i>azi2</i>] &rarr; [<i>azi1</i>, <i>azi2</i>] + [<i>d</i>, &minus;<i>d</i>], for arbitrary
127  *         <i>d</i>. (For spheres, this prescription applies when points 1 and 2 are antipodal.)
128  *     </li>
129  *     <li>
130  *         <i>s12</i> = 0 (coincident points). There are infinitely many geodesics which can be generated by setting
131  *         [<i>azi1</i>, <i>azi2</i>] &rarr; [<i>azi1</i>, <i>azi2</i>] + [<i>d</i>, <i>d</i>], for arbitrary <i>d</i>.
132  *     </li>
133  * </ul>
134  * The calculations are accurate to better than 15nm (15 nanometers) for the WGS84 ellipsoid. See Sec. 9 of
135  * <a href="https://arxiv.org/abs/1102.1215v1">arXiv:1102.1215v1</a> for details. The algorithms used by this class are
136  * based on series expansions using the flattening <i>f</i> as a small parameter. These are only accurate for |<i>f</i>|
137  * &lt; 0.02; however reasonably accurate results will be obtained for |<i>f</i>| &lt; 0.2. Here is a table with the
138  * same equatorial radius as the WGS84 ellipsoid and different values of the flattening.
139  * <pre>
140  *     |f|      error
141  *     0.01     25 nm
142  *     0.02     30 nm
143  *     0.05     10 um
144  *     0.1      1.5 mm
145  *     0.2      300 mm
146  * </pre>
147  * The algorithms are described in
148  * <ul>
149  *     <li>
150  *         C.F.F Karney, <a href="https://doi.org/10.1007/s00190-012-0578-z">Algorithms for geodesics</a>,
151  *         J. Geodesy <b>87</b>, 43&ndash;55 (2013)
152  *     </li>
153  * </ul>
154  * Example of use:
155  * <pre>
156  *     {@code
157  *     //Solve the direct geodesic problem.
158  *
159  *     //This program reads in lines with lat1, lon1, azi1, s12 and prints out lines with lat2, lon2, azi2
160  *     //(for the WGS84 ellipsoid).
161  *
162  *     import java.util.*;
163  *     import com.irurueta.navigation.geodesic.*;
164  *
165  *     public class Direct {
166  *         public static void main(String[] args) {
167  *             try {
168  *                 Scanner in = new Scanner(System.in);
169  *                 double lat1, lon1, azi1, s12;
170  *                 while (true) {
171  *                     lat1 = in.nextDouble();
172  *                     lon1 = in.nextDouble();
173  *                     azi1 = in.nextDouble();
174  *                     s12 = in.nextDouble();
175  *
176  *                     GeodesicData g = Geodesic.WGS84.direct(lat1, lon1, azi1, s12);
177  *                     System.out.println(g.lat2 + " " + g.lon2 + " " + g.azi2);
178  *                 }
179  *             } catch (Exception e) { }
180  *         }
181  *     }
182  *     }
183  * </pre>
184  */
185 @SuppressWarnings("DuplicatedCode")
186 public class Geodesic {
187 
188     /**
189      * A global instantiation of Geodesic with the parameters for the WGS84 ellipsoid.
190      */
191     public static final Geodesic WGS84 = safeInstance(Constants.EARTH_EQUATORIAL_RADIUS_WGS84,
192             Constants.EARTH_FLATTENING_WGS84);
193 
194     /**
195      * The order of the expansions used.
196      */
197     protected static final int GEODESIC_ORDER = 6;
198 
199     protected static final int NA1 = GEODESIC_ORDER;
200     protected static final int NC1 = GEODESIC_ORDER;
201     protected static final int NC1P = GEODESIC_ORDER;
202 
203     protected static final int NA2 = GEODESIC_ORDER;
204     protected static final int NC2 = GEODESIC_ORDER;
205 
206     protected static final int NA3 = GEODESIC_ORDER;
207 
208     protected static final int NA3X = NA3;
209     protected static final int NC3 = GEODESIC_ORDER;
210 
211     protected static final int NC3X = (NC3 * (NC3 - 1)) / 2;
212     protected static final int NC4 = GEODESIC_ORDER;
213 
214     protected static final int NC4X = (NC4 * (NC4 + 1)) / 2;
215 
216     /**
217      * Underflow guard. We require TINY * epsilon() &lt; 0 and TINY + epsilon() == epsilon()
218      */
219     protected static final double TINY = Math.sqrt(GeoMath.MIN);
220 
221     private static final int MAXIT1 = 20;
222     private static final int MAXIT2 = MAXIT1 + GeoMath.DIGITS + 10;
223 
224     private static final double TOL0 = GeoMath.EPSILON;
225 
226     /**
227      * Increase multiplier in defn of TOl1 from 100 to 200 to fix inverse case
228      * 52.784459412564 0 -52.784459512563990912 179.634407464943777557
229      */
230     private static final double TOL1 = 200 * TOL0;
231 
232     private static final double TOL2 = Math.sqrt(TOL0);
233 
234     /**
235      * Check on bisection interval.
236      */
237     private static final double TOLB = TOL0 * TOL2;
238 
239     private static final double XTHRESH = 1000 * TOL2;
240 
241     protected final double a;
242 
243     protected final double f;
244 
245     protected final double f1;
246 
247     protected final double e2;
248 
249     protected final double ep2;
250 
251     protected final double b;
252 
253     protected final double c2;
254 
255     private final double n;
256 
257     private final double etol2;
258 
259     private final double[] a3x;
260 
261     private final double[] c3x;
262 
263     private final double[] c4x;
264 
265     /**
266      * Constructor for an ellipsoid with.
267      *
268      * @param a equatorial radius (meters).
269      * @param f flattening of ellipsoid. Setting <i>f</i> = 0 gives a sphere. Negative <i>f</i> gives a prolate
270      *          ellipsoid.
271      * @throws GeodesicException if <i>a</i> or (1 &minus; <i>f</i>) <i>a</i> is not positive.
272      */
273     public Geodesic(final double a, final double f) throws GeodesicException {
274         this.a = a;
275         this.f = f;
276         f1 = 1 - this.f;
277         e2 = this.f * (2 - this.f);
278 
279         // e2 / (1 - e2)
280         ep2 = e2 / GeoMath.sq(f1);
281         n = this.f / (2 - this.f);
282         b = this.a * f1;
283 
284         // authalic radius squared
285         final var tmp = (e2 > 0 ? GeoMath.atanh(Math.sqrt(e2)) : Math.atan(Math.sqrt(-e2)));
286         c2 = (GeoMath.sq(this.a) + GeoMath.sq(b) * (e2 == 0 ? 1 : tmp / Math.sqrt(Math.abs(e2)))) / 2;
287 
288         // The sig12 threshold for "really short". Using the auxiliary sphere solution with dnm computed at
289         // (bet1 + bet2) / 2, the relative error in the azimuth consistency check is
290         // sig12^2 * abs(f) * min(1, 1 - f/2) / 2.
291         // (Error measured for 1/100 < b/a < 100 and abs(f) >= 1/1000. For a given f and sig12, the max error occurs for
292         // lines near the pole. If the old rule for computing dnm = (dn1 + dn2)/2 is used, then the error increases by
293         // a factor of 2.) Setting this equal to epsilon gives sig12 = etol2. Here 0.1 is a safety factor (error
294         // decreased by 100) and max(0.001, abs(f)) stops etol2 getting too large in the nearly spherical case.
295         etol2 = 0.1 * TOL2 / Math.sqrt(Math.max(0.001, Math.abs(this.f)) * Math.min(1.0, 1 - this.f / 2) / 2);
296 
297         if (!(GeoMath.isFinite(this.a) && this.a > 0)) {
298             throw new GeodesicException("Equatorial radius is not positive");
299         }
300         if (!(GeoMath.isFinite(b) && b > 0)) {
301             throw new GeodesicException("Polar semi-axis is not positive");
302         }
303         a3x = new double[NA3X];
304         c3x = new double[NC3X];
305         c4x = new double[NC4X];
306 
307         a3coeff();
308         c3coeff();
309         c4coeff();
310     }
311 
312     /**
313      * Solve the direct geodesic problem where the length of the geodesic is specified in terms of distance.
314      * If either point is at a pole, the azimuth is defined by keeping the longitude fixed, writing
315      * <i>lat</i> = &plusmn;(90&deg; &minus; &epsilon;), and taking the limit &epsilon; &rarr; 0+. An arc length greater
316      * than 180&deg; signifies a geodesic which is not the shortest path. (For a prolate ellipsoid, an additional
317      * condition is necessary for the shortest path: the longitudinal extent must not exceed of 180&deg;.)
318      *
319      * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range [&minus;90&deg;, 90&deg;].
320      * @param lon1 longitude of point 1 (degrees).
321      * @param azi1 azimuth at point 1 (degrees).
322      * @param s12  distance between point 1 and point 2 (meters); it can be negative.
323      * @return a {@link GeodesicData} object with the following fields: <i>lat1</i>, <i>lon1</i>, <i>azi1</i>,
324      * <i>lat2</i>, <i>lon2</i>, <i>azi2</i>, <i>s12</i>, <i>a12</i>. The values of <i>lon2</i> and <i>azi2</i>
325      * returned are in the range [&minus;180&deg;, 180&deg;].
326      */
327     public GeodesicData direct(final double lat1, final double lon1, final double azi1, final double s12) {
328         return direct(lat1, lon1, azi1, false, s12, GeodesicMask.STANDARD);
329     }
330 
331     /**
332      * Solve the direct geodesic problem where the length of the geodesic is specified in terms of distance with a
333      * subset of the geodesic results returned.
334      *
335      * @param lat1    latitude of point 1 (degrees).
336      * @param lon1    longitude of point 1 (degrees).
337      * @param azi1    azimuth at point 1 (degrees).
338      * @param s12     distance between point 1 and point 2 (meters); it can be negative.
339      * @param outmask a bitor'ed combination of {@link GeodesicMask} values specifying which results should be returned.
340      * @return a {@link GeodesicData} object with the fields specified by <i>outmask</i> computed. <i>lat1</i>,
341      * <i>lon1</i>, <i>azi1</i>, <i>s12</i>, and <i>a12</i> are always included in the returned result. The value of
342      * <i>lon2</i> returned is in the range [&minus;180&deg;, 180&deg;], unless the <i>outmask</i> includes the
343      * {@link GeodesicMask#LONG_UNROLL} flag.
344      */
345     public GeodesicData direct(
346             final double lat1, final double lon1, final double azi1, final double s12, final int outmask) {
347         return direct(lat1, lon1, azi1, false, s12, outmask);
348     }
349 
350     /**
351      * Solve the direct geodesic problem where the length of the geodesic is specified in terms of arc length.
352      * If either point is at a pole, the azimuth is defined by keeping the longitude fixed, writing
353      * <i>lat</i> = &plusmn;(90&deg; &minus; &epsilon;), and taking the limit &epsilon; &rarr; 0+. An arc length
354      * greater than 180&deg; signifies a geodesic which is not the shortest path. (For a prolate ellipsoid, an additional
355      * condition is necessary for the shortest path: the longitudinal extent must not exceed of 180&deg;.)
356      *
357      * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range [&minus;90&deg;, 90&deg;].
358      * @param lon1 longitude of point 1 (degrees).
359      * @param azi1 azimuth at point 1 (degrees).
360      * @param a12  arc length between point 1 and point 2 (degrees); it can be negative.
361      * @return a {@link GeodesicData} object with the following fields: <i>lat1</i>, <i>lon1</i>, <i>azi1</i>,
362      * <i>lat2</i>, <i>lon2</i>, <i>azi2</i>, <i>s12</i>, <i>a12</i>. The values of <i>lon2</i> and <i>azi2</i>
363      * returned are in the range [&minus;180&deg;, 180&deg;].
364      */
365     public GeodesicData arcDirect(final double lat1, final double lon1, final double azi1, final double a12) {
366         return direct(lat1, lon1, azi1, true, a12, GeodesicMask.STANDARD);
367     }
368 
369     /**
370      * Solve the direct geodesic problem where the length of the geodesic is specified in terms of arc length and with
371      * a subset of the geodesic results returned.
372      *
373      * @param lat1    latitude of point 1 (degrees).
374      * @param lon1    longitude of point 1 (degrees).
375      * @param azi1    azimuth at point 1 (degrees).
376      * @param a12     arc length between point 1 and point 2 (degrees); it can be negative.
377      * @param outmask a bitor'ed combination of {@link GeodesicMask} values specifying which results should be returned.
378      * @return a {@link GeodesicData} object with the fields specified by <i>outmask</i> computed. <i>lat1</i>,
379      * <i>lon1</i>, <i>azi1</i>, and <i>a12</i> are always included in the returned result. The value of <i>lon2</i>
380      * returned is in the range [&minus;180&deg;, 180&deg;], unless the <i>outmask</i> includes the
381      * {@link GeodesicMask#LONG_UNROLL} flag.
382      */
383     public GeodesicData arcDirect(
384             final double lat1, final double lon1, final double azi1, final double a12, final int outmask) {
385         return direct(lat1, lon1, azi1, true, a12, outmask);
386     }
387 
388     /**
389      * The general direct geodesic problem. {@link #direct} and {@link #arcDirect} are defined in terms of this
390      * function.
391      * The {@link GeodesicMask} values possible for <i>outmask</i> are
392      * <ul>
393      *     <li>
394      *         <i>outmask</i> |= {@link GeodesicMask#LATITUDE} for the latitude <i>lat2</i>;
395      *     </li>
396      *     <li>
397      *         <i>outmask</i> |= {@link GeodesicMask#LONGITUDE} for the longitude <i>lon2</i>;
398      *     </li>
399      *     <li>
400      *         <i>outmask</i> |= {@link GeodesicMask#AZIMUTH} for the azimuth <i>azi2</i>;
401      *     </li>
402      *     <li>
403      *         <i>outmask</i> |= {@link GeodesicMask#DISTANCE} for the distance <i>s12</i>;
404      *     </li>
405      *     <li>
406      *         <i>outmask</i> |= {@link GeodesicMask#REDUCED_LENGTH} for the reduced length <i>m12</i>;
407      *     </li>
408      *     <li>
409      *         <i>outmask</i> |= {@link GeodesicMask#GEODESIC_SCALE} for the geodesic scales <i>M12</i> and <i>M21</i>;
410      *     </li>
411      *     <li>
412      *         <i>outmask</i> |= {@link GeodesicMask#AREA} for the area <i>S12</i>;
413      *     </li>
414      *     <li>
415      *         <i>outmask</i> |= {@link GeodesicMask#LONG_UNROLL}, if set then <i>lon1</i> is unchanged and <i>lon2</i>
416      *         &minus; <i>lon1</i> indicates how many times and in what sense the geodesic encircles the ellipsoid.
417      *         Otherwise <i>lon1</i> and <i>lon2</i> are both reduced to the range [&minus;180&deg;, 180&deg;].
418      *     </li>
419      * </ul>
420      * The function value <i>a12</i> is always computed and returned and this equals <i>s12A12</i> is <i>arcmode</i> is
421      * true. If <i>outmask</i> includes {@link GeodesicMask#DISTANCE} and <i>arcmode</i> is false, then <i>s12</i> =
422      * <i>s12A12</i>. It is not necessary to include {@link GeodesicMask#DISTANCE_IN} in <i>outmask</i>; this is
423      * automatically included if <i>arcmode</i> is false.
424      *
425      * @param lat1    latitude of point 1 (degrees).
426      * @param lon1    longitude of point 1 (degrees).
427      * @param azi1    azimuth at point 1 (degrees).
428      * @param arcmode boolean flag determining the meaning of the <i>s12A12</i>.
429      * @param s12A12  <i>arcmode</i> is false, this is the distance between point 1 and point 2 (meters); otherwise it
430      *                is the arc length between point 1 and point 2 (degrees); it can be negative.
431      * @param outmask a bitor'ed combination of {@link GeodesicMask} values specifying which results should be returned.
432      * @return a {@link GeodesicData} object with the fields specified by <i>outmask</i> computed.
433      */
434     public GeodesicData direct(
435             final double lat1, final double lon1, final double azi1, final boolean arcmode, final double s12A12,
436             int outmask) {
437         // automatically supply DISTANCE_IN if necessary
438         if (!arcmode) {
439             outmask |= GeodesicMask.DISTANCE_IN;
440         }
441         return new GeodesicLine(this, lat1, lon1, azi1, outmask).position(arcmode, s12A12, outmask);
442     }
443 
444     /**
445      * Define a {@link GeodesicLine} in terms of the direct geodesic problem specified in terms of
446      * distance with all capabilities included.
447      * This function sets point 3 of the GeodesicLine to correspond to point 2 of the direct geodesic
448      * problem.
449      *
450      * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range [&minus;90&deg;, 90&deg;].
451      * @param lon1 longitude of point 1 (degrees).
452      * @param azi1 azimuth at point 1 (degrees).
453      * @param s12  distance between point 1 and point 2 (meters); it can be negative.
454      * @return a {@link GeodesicLine} object.
455      */
456     public GeodesicLine directLine(
457             final double lat1, final double lon1, final double azi1, final double s12) {
458         return directLine(lat1, lon1, azi1, s12, GeodesicMask.ALL);
459     }
460 
461     /**
462      * Define a {@link GeodesicLine} in terms of the direct geodesic problem specified in terms of
463      * distance with a subset of the capabilities included.
464      * This function sets point 3 of the GeodesicLine to correspond to point 2 of the direct geodesic
465      * problem.
466      *
467      * @param lat1 latitude of point 1 (Degrees). <i>lat1</i> should be in the range [&minus;90&deg;, 90&deg;].
468      * @param lon1 longitude of point 1 (degrees).
469      * @param azi1 azimuth at point 1 (degrees).
470      * @param s12  distance between point 1 and point 2 (meters); it can be negative.
471      * @param caps bitor'ed combination of {@link GeodesicMask} values specifying the capabilities the
472      *             GeodesicLine object should possess, i.e., which quantities can be returned in calls
473      *             to {@link GeodesicLine#position}.
474      * @return a {@link GeodesicLine} object.
475      */
476     public GeodesicLine directLine(
477             final double lat1, final double lon1, final double azi1, final double s12, final int caps) {
478         return genDirectLine(lat1, lon1, azi1, false, s12, caps);
479     }
480 
481     /**
482      * Define a {@link GeodesicLine} in terms of the direct geodesic problem specified in terms of
483      * arc length with all capabilities included.
484      * This function sets point 3 of the GeodesicLine to correspond to point 2 of the direct geodesic
485      * problem.
486      *
487      * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range [&minus;90&deg;, 90&deg;].
488      * @param lon1 longitude of point 1 (degrees).
489      * @param azi1 azimuth at point 1 (degrees).
490      * @param a12  arc length between point 1 and point 2 (degrees); it can be negative.
491      * @return a {@link GeodesicLine} object.
492      */
493     public GeodesicLine arcDirectLine(final double lat1, final double lon1, final double azi1, final double a12) {
494         return arcDirectLine(lat1, lon1, azi1, a12, GeodesicMask.ALL);
495     }
496 
497     /**
498      * Define a {@link GeodesicLine} in terms of the direct geodesic problem specified in terms of
499      * arc length with a subset of the capabilities included.
500      * This function sets point 3 of the GeodesicLine to correspond to point 2 of the direct geodesic problem.
501      *
502      * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range [&minus;90&deg;, 90&deg;].
503      * @param lon1 longitude of point 1 (degrees).
504      * @param azi1 azimuth at point 1 (degrees).
505      * @param a12  arc length between point 1 and point 2 (degrees); it can be negative.
506      * @param caps bitor'ed combination of {@link GeodesicMask} values specifying the capabilities the
507      *             GeodesicLine object should possess, i.e., which quantities can be returned in calls
508      *             to {@link GeodesicLine#position}.
509      * @return a {@link GeodesicLine} object.
510      */
511     public GeodesicLine arcDirectLine(
512             final double lat1, final double lon1, final double azi1, final double a12, final int caps) {
513         return genDirectLine(lat1, lon1, azi1, true, a12, caps);
514     }
515 
516     /**
517      * Define a {@link GeodesicLine} in terms of the direct geodesic problem specified in terms of
518      * either distance or arc length with a subset of the capabilities included.
519      * This function sets point 3 of the GeodesicLine to correspond to point 2 of the direct geodesic
520      * problem.
521      *
522      * @param lat1    latitude of point 1 (degrees). <i>lat1</i> should be in the range [&minus;90&deg;, 90&deg;].
523      * @param lon1    longitude of point 1 (degrees).
524      * @param azi1    azimuth at point 1 (degrees).
525      * @param arcmode boolean flag determining the meaning of the <i>s12A12</i>.
526      * @param s12A12  if <i>arcmode</i> is false, this is the distance between point 1 and point 2 (meters); otherwise
527      *                it is the arc length between point 1 and point 2 (degrees); it can be negative.
528      * @param caps    bitor'ed combination of {@link GeodesicMask} values specifying the capabilities the
529      *                GeodesicLine object should possess, i.e., which quantities can be returned in calls
530      *                to {@link GeodesicLine#position}.
531      * @return a {@link GeodesicLine} object.
532      */
533     public GeodesicLine genDirectLine(
534             final double lat1, final double lon1, double azi1, final boolean arcmode, final double s12A12, int caps) {
535         azi1 = GeoMath.angNormalize(azi1);
536         final double salp1;
537         final double calp1;
538 
539         // Guard against underflow in salp0. Also -0 is converted to +0.
540         final var p = GeoMath.sincosd(GeoMath.angRound(azi1));
541         salp1 = p.getFirst();
542         calp1 = p.getSecond();
543 
544         // Automatically supply DISTANCE_IN if necessary
545         if (!arcmode) {
546             caps |= GeodesicMask.DISTANCE_IN;
547         }
548 
549         return new GeodesicLine(this, lat1, lon1, azi1, salp1, calp1, caps, arcmode, s12A12);
550     }
551 
552     /**
553      * Solve the inverse geodesic problem.
554      * <i>lat1</i> and <i>lat2</i> should be in the range [&minus;90&deg;, 90&deg;]. The values of
555      * <i>azi1</i> and <i>azi2</i> returned are in the range [&minus;180&deg;, 180&deg;].
556      * If either point is at a pole, the azimuth is defined by keeping the longitude fixed, writing
557      * <i>lat</i> = &plusmn;(90&deg; &minus; &epsilon;), taking the limit &epsilon; &rarr; 0+.
558      * The solution to the inverse problem is found using Newton's method. If this fails to converge
559      * (this is very unlikely in geodetic applications but does occur for very eccentric ellipsoids),
560      * then the bisection method is used to refine the solution.
561      *
562      * @param lat1 latitude of point 1 (degrees).
563      * @param lon1 longitude of point 1 (degrees).
564      * @param lat2 latitude of point 2 (degrees).
565      * @param lon2 longitude of point 2 (degrees).
566      * @return a {@link GeodesicData} object with the following fields: <i>lat1</i>, <i>lon1</i>,
567      * <i>azi1</i>, <i>lat2</i>, <i>lon2</i>, <i>azi2</i>, <i>s12</i>, <i>a12</i>.
568      */
569     public GeodesicData inverse(final double lat1, final double lon1, final double lat2, final double lon2) {
570         return inverse(lat1, lon1, lat2, lon2, GeodesicMask.STANDARD);
571     }
572 
573     /**
574      * Solve the inverse geodesic problem with a subset of the geodesic results returned.
575      * The {@link GeodesicMask} values possible for <i>outmask</i> are
576      * <ul>
577      *     <li>
578      *         <i>outmask</i> |= {@link GeodesicMask#DISTANCE} for the distance <i>s12</i>;
579      *     </li>
580      *     <li>
581      *         <i>outmask</i> |= {@link GeodesicMask#AZIMUTH} for the latitude <i>azi2</i>.
582      *     </li>
583      *     <li>
584      *         <i>outmask</i> |= {@link GeodesicMask#REDUCED_LENGTH} for the reduced length
585      *         <i>m12</i>;
586      *     </li>
587      *     <li>
588      *         <i>outmask</i> |= {@link GeodesicMask#GEODESIC_SCALE} for the geodesic scales
589      *         <i>M12</i> and <i>M21</i>;
590      *     </li>
591      *     <li>
592      *         <i>outmask</i> |= {@link GeodesicMask#AREA} for the area <i>S12</i>;
593      *     </li>
594      *     <li>
595      *         <i>outmask</i> |= {@link GeodesicMask#ALL} for all of the above.
596      *     </li>
597      *     <li>
598      *         <i>outmask</i> |= {@link GeodesicMask#LONG_UNROLL}, if set then <i>lon1</i> is
599      *         unchanged and <i>lon2</i> &minus; <i>lon1</i> indicates whether the geodesic is
600      *         east going or west going. Otherwise <i>lon1</i> and <i>lon2</i> are both reduced to
601      *         the range [&minus;180&deg;, 180&deg;].
602      *     </li>
603      * </ul>
604      *
605      * @param lat1    latitude of point 1 (degrees).
606      * @param lon1    longitude of point 1 (degrees).
607      * @param lat2    latitude of point 2 (degrees).
608      * @param lon2    longitude of point 2 (degrees)
609      * @param outmask a bitor'ed combination of {@link GeodesicMask} values specifying which
610      *                results should be returned.
611      * @return a {@link GeodesicData} object with the fields specified by <i>outmask</i> computed.
612      * <i>lat1</i>, <i>lon1</i>, <i>lat2</i>, <i>lon2</i>, and <i>a12</i> are always included in the
613      * returned result.
614      */
615     public GeodesicData inverse(
616             final double lat1, final double lon1, final double lat2, final double lon2, int outmask) {
617         outmask &= GeodesicMask.OUT_MASK;
618         final var result = inverseInt(lat1, lon1, lat2, lon2, outmask);
619         final var r = result.g;
620 
621         if ((outmask & GeodesicMask.AZIMUTH) != 0) {
622             r.setAzi1(GeoMath.atan2d(result.salp1, result.calp1));
623             r.setAzi2(GeoMath.atan2d(result.salp2, result.calp2));
624         }
625         return r;
626     }
627 
628     /**
629      * Define a {@link GeodesicLine} in terms of the inverse geodesic problem with all capabilities
630      * included.
631      * This function sets point 3 of the GeodesicLine to correspond to point 2 of the inverse
632      * geodesic problem.
633      * <i>lat2</i> and <i>lat2</i> should be in the range [&minus;90&deg;, 90&deg;].
634      *
635      * @param lat1 latitude of point 1 (degrees).
636      * @param lon1 longitude of point 1 (degrees).
637      * @param lat2 latitude of point 2 (degrees).
638      * @param lon2 longitude of point 2 (degrees).
639      * @return a {@link GeodesicLine} object.
640      */
641     public GeodesicLine inverseLine(final double lat1, final double lon1, final double lat2, final double lon2) {
642         return inverseLine(lat1, lon1, lat2, lon2, GeodesicMask.ALL);
643     }
644 
645     /**
646      * Define a {@link GeodesicLine} in terms of the inverse geodesic problem with a subet of the
647      * capabilities included.
648      * This function sets point 3 of the GeodesicLine to correspond to point 2 of the inverse
649      * geodesic problem.
650      * <i>lat1</i> and <i>lat2</i> should be in the range [&minus;90&deg;, 90&deg;].
651      *
652      * @param lat1 latitude of point 1 (degrees).
653      * @param lon1 longitude of point 1 (degrees).
654      * @param lat2 latitude of point 2 (degrees).
655      * @param lon2 longitude of point 2 (degrees).
656      * @param caps bitor'ed combination of {@link GeodesicMask} values specifying the capabilities
657      *             the GeodesicLine object should possess, i.e., which quantities can be returned
658      *             in calls to {@link GeodesicLine#position}.
659      * @return a {@link GeodesicLine} object.
660      */
661     public GeodesicLine inverseLine(
662             final double lat1, final double lon1, final double lat2, final double lon2, int caps) {
663         final var result = inverseInt(lat1, lon1, lat2, lon2, 0);
664         final var salp1 = result.salp1;
665         final var calp1 = result.calp1;
666         final var azi1 = GeoMath.atan2d(salp1, calp1);
667         final var a12 = result.g.getA12();
668         // ensure that a12 can be converted to a distance
669         if ((caps & (GeodesicMask.OUT_MASK & GeodesicMask.DISTANCE_IN)) != 0) {
670             caps |= GeodesicMask.DISTANCE;
671         }
672         return new GeodesicLine(this, lat1, lon1, azi1, salp1, calp1, caps, true, a12);
673     }
674 
675     /**
676      * Set up to compute several points on a single geodesic with all capabilities included.
677      * If the point is at a pole, the azimuth is defined by keeping the <i>lon1</i> fixed, writing
678      * <i>lat1</i> = &plusmn; (90 &minus; &epsilon;), taking the limit &epsilon; &rarr; 0+.
679      *
680      * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range
681      *             [&minus;90&deg;, 90&deg;].
682      * @param lon1 longitude of point 1 (degrees).
683      * @param azi1 azimuth at point 1 (degrees).
684      * @return a {@link GeodesicLine} object. The full set of capabilities is included.
685      */
686     public GeodesicLine line(final double lat1, final double lon1, final double azi1) {
687         return line(lat1, lon1, azi1, GeodesicMask.ALL);
688     }
689 
690     /**
691      * Set up to compute several points on a single geodesic with a subset of the capabilities
692      * included.
693      * The {@link GeodesicMask} values are:
694      * <ul>
695      *     <li>
696      *         <i>caps</i> |= {@link GeodesicMask#LATITUDE} for the latitude <i>lat2</i>; this is
697      *         added automatically;
698      *     </li>
699      *     <li>
700      *         <i>caps</i> |= {@link GeodesicMask#LONGITUDE} for the longitude <i>lon2</i>;
701      *     </li>
702      *     <li>
703      *         <i>caps</i> |= {@link GeodesicMask#AZIMUTH} for the azimuth <i>azi2</i>; this is
704      *         added automatically;
705      *     </li>
706      *     <li>
707      *         <i>caps</i> |= {@link GeodesicMask#DISTANCE} for the distance <i>s12</i>;
708      *     </li>
709      *     <li>
710      *         <i>caps</i> |= {@link GeodesicMask#REDUCED_LENGTH} for the reduced length <i>m12</i>;
711      *     </li>
712      *     <li>
713      *         <i>caps</i> |= {@link GeodesicMask#GEODESIC_SCALE} for the geodesic scales <i>M12</i>
714      *         and <i>M21</i>;
715      *     </li>
716      *     <li>
717      *         <i>caps</i> |= {@link GeodesicMask#AREA} for the area <i>S12</i>;
718      *     </li>
719      *     <li>
720      *         <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN} permits the length of the geodesic to
721      *         be given in terms of <i>s12</i>; without this capability the length can only be specified
722      *         in terms of arc length;
723      *     </li>
724      *     <li>
725      *         <i>caps</i> |= {@link GeodesicMask#ALL} for all of the above.
726      *     </li>
727      * </ul>
728      * If the point is at a pole, the azimuth is defined by keeping <i>lon1</i> fixed, writing
729      * <i>lat1</i> = &plusmn; (90 &minus; &epsilon;), and taking the limit &epsilon; &rarr; 0+.
730      *
731      * @param lat1 latitude of point 1 (degrees).
732      * @param lon1 longitude of point 1 (degrees).
733      * @param azi1 azimuth at point 1 (degrees).
734      * @param caps bitor'ed combination of {@link GeodesicMask} values specifying which
735      *             quantities can be returned in calls to {@link GeodesicLine#position}.
736      * @return a {@link GeodesicLine} object.
737      */
738     public GeodesicLine line(final double lat1, final double lon1, final double azi1, final int caps) {
739         return new GeodesicLine(this, lat1, lon1, azi1, caps);
740     }
741 
742     /**
743      * Gets the equatorial radius of the ellipsoid (meters). This is the value used in the
744      * constructor.
745      *
746      * @return <i>a</i> the equatorial radius of the ellipsoid (meters).
747      */
748     public double getMajorRadius() {
749         return a;
750     }
751 
752     /**
753      * Gets the flattening of the ellipsoid. This is the value used in the constructor.
754      *
755      * @return <i>f</i> the flattening of the ellipsoid.
756      */
757     public double getFlattening() {
758         return f;
759     }
760 
761     /**
762      * Total area of ellipsoid in meters<sup>2</sup>. The area of a polygon encircling a pole can
763      * be found by adding ellipsoidArea()/2 to the sum of <i>S12</i> for each side of the polygon.
764      *
765      * @return total area of ellipsoid in meters<sup>2</sup>.
766      */
767     public double getEllipsoidArea() {
768         return 4 * Math.PI * c2;
769     }
770 
771     /**
772      * This is a reformulation of the geodesic problem. The notation is as follows:
773      * - at a general point (no suffix or 1 or 2 as suffix)
774      * - phi = latitude
775      * - beta = latitude on auxiliary sphere
776      * - omega = longitude on auxiliary sphere
777      * - lambda = longitude
778      * - alpha = azimuth of great circle
779      * - sigma = arc length along great circle
780      * - s = distance
781      * - tau = scaled distance (= sigma at multiples of pi/2)
782      * - at northwards equator crossing
783      * - beta = phi = 0
784      * - omega = lambda = 0
785      * - alpha = alpha0
786      * - sigma = s = 0
787      * - a 12 suggix means a difference, e.g., s12 = s2 - s1.
788      * - s and c prefixes mean sin and cos
789      *
790      * @param sinp sinus of p
791      * @param sinx sinus of x
792      * @param cosx cosinus of x
793      * @param c    c
794      * @return sin cos series.
795      */
796     protected static double sinCosSeries(final boolean sinp, final double sinx, final double cosx, final double[] c) {
797         // evaluate
798         // y = sinp ? sum(c[i] * sin(2 * i * x), i, 1, n) :
799         //       sum(c[i] * cos((2 * i + 1) * x), i, 0, n - 1)
800         // using Clenshaw summation. N.B. c[0] is unused for sin series
801         // Approx operation count = (n + 5) mult and (2 * n + 2) add
802 
803         // point to one beyond last element
804         var k = c.length;
805         var n = k - (sinp ? 1 : 0);
806 
807         // 2 * cos(2 * x)
808         final var ar = 2 * (cosx - sinx) * (cosx + sinx);
809         // accumulators for sum
810         var y0 = (n & 1) != 0 ? c[--k] : 0;
811         var y1 = 0.0;
812 
813         // now n is even
814         n /= 2;
815         while (n-- != 0) {
816             // unroll loop x 2, so accumulators return to their original role
817             y1 = ar * y0 - y1 + c[--k];
818             y0 = ar * y1 - y0 + c[--k];
819         }
820 
821         // sin(2 * x) * y0 or cos(x) * (y0 - y1)
822         return sinp ? 2 * sinx * cosx * y0 : cosx * (y0 - y1);
823     }
824 
825     protected double a3f(final double eps) {
826         // evaluate a3
827         return GeoMath.polyval(NA3 - 1, a3x, 0, eps);
828     }
829 
830     protected void c3f(final double eps, final double[] c) {
831         // evaluate c3 coeffs
832         // elements c[1] thru c[NC3 - 1] are set
833         var mult = 1.0;
834         var o = 0;
835 
836         // 1 is index of c3[l]
837         for (var l = 1; l < NC3; ++l) {
838             // order of polynomial in eps
839             final var m = NC3 - l - 1;
840             mult *= eps;
841             c[l] = mult * GeoMath.polyval(m, c3x, o, eps);
842             o += m + 1;
843         }
844     }
845 
846     protected void c4f(final double eps, final double[] c) {
847         // evaluate c4 coeffs
848         // elements c[0] thru c[NC4 - 1] are set
849         var mult = 1.0;
850         var o = 0;
851 
852         // l is index of c4[l]
853         for (var l = 0; l < NC4; ++l) {
854             // order of polynomial in eps
855             final var m = NC4 - l - 1;
856             c[l] = mult * GeoMath.polyval(m, c4x, o, eps);
857             o += m + 1;
858             mult *= eps;
859         }
860     }
861 
862     // the scale factor a1 - 1 = mean value of (d/dsigma) i1 - 1
863     protected static double a1m1f(final double eps) {
864         final double[] coeffs = {
865                 // (1 - eps) * a1 - 1, polynomial in eps2 of order3
866                 1, 4, 64, 0, 256,
867         };
868         final var m = NA1 / 2;
869         final var t = GeoMath.polyval(m, coeffs, 0, GeoMath.sq(eps)) / coeffs[m + 1];
870         return (t + eps) / (1 - eps);
871     }
872 
873     // The coefficients c1[l] in the Fourier expansion of b1
874     protected static void c1f(final double eps, final double[] c) {
875         final double[] coeff = {
876                 // c1[1]/eps^1, polynomial in eps2 of order 2
877                 -1, 6, -16, 32,
878                 // c1[2]/eps^2, polynomial in eps2 of order 2
879                 -9, 64, -128, 2048,
880                 // c1[3]/eps^3, polynomial in eps2 of order 1
881                 9, -16, 768,
882                 // c1[4]/eps^4, polynomial in eps2 of order 1
883                 3, -5, 512,
884                 // c1[5]/eps^5, polynomial in eps2 of order 0
885                 -7, 1280,
886                 // c1[6]/eps^6, polynomial in eps2 of order 0
887                 -7, 2048,
888         };
889 
890         final var eps2 = GeoMath.sq(eps);
891         var d = eps;
892         var o = 0;
893         for (var l = 1; l <= NC1; ++l) {
894             // l is index of c1p[l]
895             // order of polynomial in eps^2
896             final var m = (NC1 - l) / 2;
897             c[l] = d * GeoMath.polyval(m, coeff, o, eps2) / coeff[o + m + 1];
898             o += m + 2;
899             d *= eps;
900         }
901     }
902 
903     // The coefficients c1p[l] in the Fourier expansion of b1p
904     protected static void c1pf(final double eps, final double[] c) {
905         final double[] coeff = {
906                 // c1p[l]/eps^1, polynomial in eps2 of order 2
907                 205, -432, 768, 1536,
908                 // c1p[2]/eps^2, polynomial in eps2 of order 2
909                 4005, -4736, 3840, 12288,
910                 // c1p[3]/eps^3, polynomial in eps2 of order 1
911                 -225, 116, 384,
912                 // c1p[4]/eps^4, polynomial in eps2 of order 1
913                 -7173, 2695, 7680,
914                 // c1p[5]/eps^5, polynomial in eps2 of order 0
915                 3467, 7680,
916                 // c1p[6]/eps^6, polynomial in eps2 of order 0
917                 38081, 61440,
918         };
919 
920         final var eps2 = GeoMath.sq(eps);
921         var d = eps;
922         var o = 0;
923 
924         // l is index of c1p[l]
925         for (var l = 1; l <= NC1P; ++l) {
926             // order of polynomial in eps^2
927             final var m = (NC1P - l) / 2;
928             c[l] = d * GeoMath.polyval(m, coeff, o, eps2) / coeff[o + m + 1];
929             o += m + 2;
930             d *= eps;
931         }
932     }
933 
934     // the scale factor a2 - 1 = mean value of (d/dsigma)i2 - 1
935     protected static double a2m1f(final double eps) {
936         final double[] coeff = {
937                 // (eps + 1)*a2 - 1, polynomial in eps2 of order 3
938                 -11, -28, -192, 0, 256,
939         };
940 
941         final var m = NA2 / 2;
942         final var t = GeoMath.polyval(m, coeff, 0, GeoMath.sq(eps)) / coeff[m + 1];
943         return (t - eps) / (1 + eps);
944     }
945 
946     // the coefficients c2[l] in the Fourier expansion of b2
947     protected static void c2f(final double eps, final double[] c) {
948         final double[] coeff = {
949                 // c2[1]/eps^1, polynomial in eps2 of order 2
950                 1, 2, 16, 32,
951                 // c2[2]/eps^2, polynomial in eps2 of order 2
952                 35, 64, 384, 2048,
953                 // c2[3]/eps^3, polynomial in eps2 of order 1
954                 15, 80, 768,
955                 // c2[4]/eps^4, polynomial in eps2 of order 1
956                 7, 35, 512,
957                 // c2[5]/eps^5, polynomial in eps2 of order 0
958                 63, 1280,
959                 // c2[6]/eps^6, polynomial in eps2 of order 0
960                 77, 2048,
961         };
962 
963         final var eps2 = GeoMath.sq(eps);
964         var d = eps;
965         var o = 0;
966 
967         // l is index of c2[l]
968         for (var l = 1; l <= NC2; ++l) {
969             // order of polynomial in eps^2
970             final var m = (NC2 - l) / 2;
971             c[l] = d * GeoMath.polyval(m, coeff, o, eps2) / coeff[o + m + 1];
972             o += m + 2;
973             d *= eps;
974         }
975     }
976 
977     // The scale factor a3 = mean value of (d/dsigma)i3
978     private void a3coeff() {
979         final double[] coeff = {
980                 // a3, coeff of eps^5, polynomial in n of order 0
981                 -3, 128,
982                 // a3, coeff of eps^4, polynomial in n of order 1
983                 -2, -3, 64,
984                 // a3, coeff of eps^3, polynomial in n of order 2
985                 -1, -3, -1, 16,
986                 // a3, coeff of eps^2, polynomial in n of order 2
987                 3, -1, -2, 8,
988                 // a3, coeff of eps^1, polynomial in n of order 1
989                 1, -1, 2,
990                 // a3, coeff of eps^0, polynomial in n of order 0
991                 1, 1,
992         };
993 
994         var o = 0;
995         var k = 0;
996 
997         // coeff of eps^j
998         for (var j = NA3 - 1; j >= 0; --j) {
999             // order of polynomial in n
1000             final var m = Math.min(NA3 - j - 1, j);
1001             a3x[k++] = GeoMath.polyval(m, coeff, o, n) / coeff[o + m + 1];
1002             o += m + 2;
1003         }
1004     }
1005 
1006     // The coefficients c3[l] in the Fourier expansion of b3
1007     private void c3coeff() {
1008         final double[] coeff = {
1009                 // c3[1], coeff of eps^5, polynomial in n of order 0
1010                 3, 128,
1011                 // c3[1], coeff of eps^4, polynomial in n of order 1
1012                 2, 5, 128,
1013                 // c3[1], coeff of eps^3, polynomial in n of order 2
1014                 -1, 3, 3, 64,
1015                 // c3[1], coeff of eps^2, polynomial in n of order 2
1016                 -1, 0, 1, 8,
1017                 // c3[1], coeff of eps^1, polynomial in n of order 1
1018                 -1, 1, 4,
1019                 // c3[2], coeff of eps^5, polynomial in n of order 0
1020                 5, 256,
1021                 // c3[2], coeff of eps^4, polynomial in n of order 1
1022                 1, 3, 128,
1023                 // c3[2], coeff of eps^3, polynomial in n of order 2
1024                 -3, -2, 3, 64,
1025                 // c3[2], coeff of eps^2, polynomial in n of order 2
1026                 1, -3, 2, 32,
1027                 // c3[3], coeff of eps^5, polynomial in n of order 0
1028                 7, 512,
1029                 // c3[3], coeff of eps^4, polynomial in n of order 1
1030                 -10, 9, 384,
1031                 // c3[3], coeff of eps^3, polynomial in n of order 2
1032                 5, -9, 5, 192,
1033                 // c3[4], coeff of eps^5, polynomial in n of order 0
1034                 7, 512,
1035                 // c3[4], coeff of eps^4, polynomial in n of order 1
1036                 -14, 7, 512,
1037                 // c3[5], coeff of eps^5, polynomial in n of order 0
1038                 21, 2560,
1039         };
1040 
1041         var o = 0;
1042         var k = 0;
1043         // l is index of c3[l]
1044         for (var l = 1; l < NC3; ++l) {
1045             for (var j = NC3 - 1; j >= l; --j) {
1046                 // coeff of eps^j
1047                 // order of polynomial in n
1048                 final var m = Math.min(NC3 - j - 1, j);
1049                 c3x[k++] = GeoMath.polyval(m, coeff, o, n) / coeff[o + m + 1];
1050                 o += m + 2;
1051             }
1052         }
1053     }
1054 
1055     private void c4coeff() {
1056         final double[] coeff = {
1057                 // c4[0], coeff of eps^5, polynomial in n of order 0
1058                 97, 15015,
1059                 // c4[0], coeff of eps^4, polynomial in n of order 1
1060                 1088, 156, 45045,
1061                 // c4[0], coeff of eps^3, polynomial in n of order 2
1062                 -224, -4784, 1573, 45045,
1063                 // c4[0], coeff of eps^2, polynomial in n of order 3
1064                 -10656, 14144, -4576, -858, 45045,
1065                 // c4[0], coeff of eps^1, polynomial in n of order 4
1066                 64, 624, -4576, 6864, -3003, 15015,
1067                 // c4[0], coeff of eps^0, polynomial in n of order 5
1068                 100, 208, 572, 3432, -12012, 30030, 45045,
1069                 // c4[1], coeff of eps^5, polynomial in n of order 0
1070                 1, 9009,
1071                 // c4[1], coeff of eps^4, polynomial in n of order 1
1072                 -2944, 468, 135135,
1073                 // c4[1], coeff of eps^3, polynomial in n of order 2
1074                 5792, 1040, -1287, 135135,
1075                 // c4[1], coeff of eps^2, polynomial in n of order 3
1076                 5952, -11648, 9152, -2574, 135135,
1077                 // c4[1], coeff of eps^1, polynomial in n of order 4
1078                 -64, -624, 4576, -6864, 3003, 135135,
1079                 // c4[2], coeff of eps^5, polynomial in n of order 0
1080                 8, 10725,
1081                 // c4[2], coeff of eps^4, polynomial in n of order 1
1082                 1856, -936, 225225,
1083                 // c4[2], coeff of eps^3, polynomial in n of order 2
1084                 -8448, 4992, -1144, 225225,
1085                 // c4[2], coeff of eps^2, polynomial in n of order 3
1086                 -1440, 4160, -4576, 1716, 225225,
1087                 // c4[3], coeff of eps^5, polynomial in n of order 0
1088                 -136, 63063,
1089                 // c4[3], coeff of eps^4, polynomial in n of order 1
1090                 1024, -208, 105105,
1091                 // c4[3], coeff of eps^3, polynomial in n of order 2
1092                 3584, -3328, 1144, 315315,
1093                 // c4[4], coeff of eps^5, polynomial in n of order 0
1094                 -128, 135135,
1095                 // c4[4], coeff of eps^4, polynomial in n of order 1
1096                 -2560, 832, 405405,
1097                 // c4[5], coeff of eps^5, polynomial in n of order 0
1098                 128, 99099,
1099         };
1100         var o = 0;
1101         var k = 0;
1102         // l is index of c3[l]
1103         for (var l = 0; l < NC4; ++l) {
1104             // coeff of eps^j
1105             for (var j = NC4 - 1; j >= l; --j) {
1106                 // order of polynomial in n
1107                 final var m = NC4 - j - 1;
1108                 c4x[k++] = GeoMath.polyval(m, coeff, o, n) / coeff[o + m + 1];
1109                 o += m + 2;
1110             }
1111         }
1112     }
1113 
1114     private InverseData inverseInt(
1115             double lat1, final double lon1, double lat2, final double lon2, final int outmask) {
1116         final var result = new InverseData();
1117         final var r = result.g;
1118 
1119         // Compute longitude difference (angDiff does this carefully). Result is in [-180, 180] but
1120         // -180 is only for west-going geodesics. 180 is for east-going and meridional geodesics.
1121         lat1 = GeoMath.latFix(lat1);
1122         lat2 = GeoMath.latFix(lat2);
1123         r.setLat1(lat1);
1124         r.setLat2(lat2);
1125 
1126         // if really close to the equator, treat as on equator
1127         lat1 = GeoMath.angRound(lat1);
1128         lat2 = GeoMath.angRound(lat2);
1129 
1130         var p = GeoMath.angDiff(lon1, lon2);
1131         var lon12 = p.getFirst();
1132         var lon12s = p.getSecond();
1133 
1134         if ((outmask & GeodesicMask.LONG_UNROLL) != 0) {
1135             r.setLon1(lon1);
1136             r.setLon2((lon1 + lon12) + lon12s);
1137         } else {
1138             r.setLon1(GeoMath.angNormalize(lon1));
1139             r.setLon2(GeoMath.angNormalize(lon2));
1140         }
1141 
1142         // make longitude difference positive
1143         var lonsign = lon12 >= 0 ? 1 : -1;
1144 
1145         // if very close to being on the same half-meridian, then make it so
1146         lon12 = lonsign * GeoMath.angRound(lon12);
1147         lon12s = GeoMath.angRound((180 - lon12) - lonsign * lon12s);
1148         final var lam12 = Math.toRadians(lon12);
1149 
1150         p = GeoMath.sincosd(lon12 > 90 ? lon12s : lon12);
1151         var slam12 = p.getFirst();
1152         var clam12 = (lon12 > 90 ? -1 : 1) * p.getSecond();
1153 
1154         // swap points so that point with higher (abs) latitude is point 1
1155         // if one latitude is a nan, then it becomes lat1
1156         final var swapp = Math.abs(lat1) < Math.abs(lat2) ? -1 : 1;
1157         if (swapp < 0) {
1158             lonsign *= -1;
1159 
1160             final double t = lat1;
1161             lat1 = lat2;
1162             lat2 = t;
1163         }
1164 
1165         // make lat1 <= 0
1166         final var latsign = lat1 < 0 ? 1 : -1;
1167         lat1 *= latsign;
1168         lat2 *= latsign;
1169 
1170         // now we have
1171         // 0 <= lon2 <= 180
1172         // -90 <= lat1 <= 0
1173         // lat1 <= lat2 <= -lat1
1174         // longsign, swapp, latsign register the transformation to bring the coordinates to this
1175         // canonical form. In all cases, 1 means no change was made. We make these transformations
1176         // so that there are few cases to check, e.g., on verifying quadrants in atan2. In addition,
1177         // this enforces some symmetries in the results returned.
1178 
1179         var m12x = Double.NaN;
1180         var s12x = Double.NaN;
1181 
1182         p = GeoMath.sincosd(lat1);
1183         var sbet1 = f1 * p.getFirst();
1184         var cbet1 = p.getSecond();
1185 
1186         // ensure cbet1 = +epsilon at poles; doing the fix on beta means that sig12 will be <= 2*tiny
1187         // for two points at the same pole.
1188         p = GeoMath.norm(sbet1, cbet1);
1189         sbet1 = p.getFirst();
1190         cbet1 = p.getSecond();
1191 
1192         cbet1 = Math.max(TINY, cbet1);
1193 
1194         p = GeoMath.sincosd(lat2);
1195         var sbet2 = f1 * p.getFirst();
1196         var cbet2 = p.getSecond();
1197 
1198         // ensure cbet2 = +epsilon at poles
1199         p = GeoMath.norm(sbet2, cbet2);
1200         sbet2 = p.getFirst();
1201         cbet2 = p.getSecond();
1202 
1203         cbet2 = Math.max(TINY, cbet2);
1204 
1205         // if cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the |bet1| - |bet2|.
1206         // Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is a better measure.
1207         // This logic is used in assigning calp2 in lambda12.
1208         // Sometimes these quantities vanish and in that case we force bet2 = +/- bet1 exactly.
1209         // An example where it is necessary is the inverse problem
1210         // 48.522876735459 0 -48.522876735458293 179.599720456223079643
1211 
1212         if (cbet1 < -sbet1) {
1213             if (cbet2 == cbet1) {
1214                 sbet2 = sbet2 < 0 ? sbet1 : -sbet1;
1215             }
1216         } else {
1217             if (Math.abs(sbet2) == -sbet1) {
1218                 cbet2 = cbet1;
1219             }
1220         }
1221 
1222         final var dn1 = Math.sqrt(1.0 + ep2 * GeoMath.sq(sbet1));
1223         final var dn2 = Math.sqrt(1.0 + ep2 * GeoMath.sq(sbet2));
1224 
1225         double sig12;
1226         var calp1 = Double.NaN;
1227         var salp1 = Double.NaN;
1228         var calp2 = Double.NaN;
1229         var salp2 = Double.NaN;
1230         var a12 = Double.NaN;
1231 
1232         // index zero elements of these arrays are unused
1233         final var c1a = new double[NC1 + 1];
1234         final var c2a = new double[NC2 + 1];
1235         final var c3a = new double[NC3];
1236 
1237         var meridian = lat1 == -90 || slam12 == 0;
1238 
1239         if (meridian) {
1240             // endpoints are on a single full meridian, so the geodesic might lie on a meridian
1241 
1242             // head to the target longitude
1243             calp1 = clam12;
1244             salp1 = slam12;
1245 
1246             // at the target we're heading north
1247             calp2 = 1;
1248             salp2 = 0;
1249 
1250             // tan(bet) = tan(sig) * cos(alp)
1251             //noinspection all
1252             final var ssig1 = sbet1;
1253             final var csig1 = calp1 * cbet1;
1254             final var csig2 = calp2 * cbet2;
1255 
1256             // sig12 = sig2 - sig1
1257             sig12 = Math.atan2(Math.max(0.0, csig1 * sbet2 - ssig1 * csig2),
1258                     csig1 * csig2 + ssig1 * sbet2);
1259 
1260             final var v = lengths(n, sig12, ssig1, csig1, dn1, sbet2, csig2, dn2, cbet1, cbet2,
1261                     outmask | GeodesicMask.DISTANCE | GeodesicMask.REDUCED_LENGTH, c1a, c2a);
1262             s12x = v.s12b;
1263             m12x = v.m12b;
1264 
1265             if ((outmask & GeodesicMask.GEODESIC_SCALE) != 0) {
1266                 r.setScaleM12(v.m12);
1267                 r.setScaleM21(v.m21);
1268             }
1269 
1270             // add the check for sig12 since zero length geodesics might yield m12 < 0. Test case was
1271             // echo 20.001 0 20.001 0 | GeodSolve -i
1272             // in fact, we will have sig12 > pi/2 for meridional geodesic which is not the shortest path.
1273             if (sig12 < 1 || m12x >= 0) {
1274                 // need at least 2, to handle 90 0 90 180
1275                 if (sig12 < 3 * TINY) {
1276                     sig12 = m12x = s12x = 0;
1277                 }
1278                 m12x *= b;
1279                 s12x *= b;
1280                 a12 = Math.toDegrees(sig12);
1281             } else {
1282                 // m12 < 0, i.e., prolate and too close to anti-podal
1283                 meridian = false;
1284             }
1285         }
1286 
1287         var omg12 = Double.NaN;
1288         var somg12 = 2.0;
1289         var comg12 = Double.NaN;
1290         // and sbet2 == 0 mimic the way Lambda12 works with calp1 = 0
1291         if (!meridian && sbet1 == 0.0 && (f <= 0.0 || lon12s >= f * 180.0)) {
1292             // geodesic runs along equator
1293             calp1 = calp2 = 0.0;
1294             salp1 = salp2 = 1.0;
1295             s12x = a * lam12;
1296             sig12 = omg12 = lam12 / f1;
1297             m12x = b * Math.sin(sig12);
1298             if ((outmask & GeodesicMask.GEODESIC_SCALE) != 0) {
1299                 final var value = Math.cos(sig12);
1300                 r.setScaleM12(value);
1301                 r.setScaleM21(value);
1302             }
1303             a12 = lon12 / f1;
1304 
1305         } else if (!meridian) {
1306             // now point1 and point2 belong within a hemisphere bounded by a
1307             // meridian and geodesic is neither meridional nor equatorial
1308 
1309             // figure a starting point for Newton's method
1310             final var iv = inverseStart(sbet1, cbet1, dn1, sbet2, cbet2, dn2, lam12, slam12, clam12, c1a, c2a);
1311             sig12 = iv.sig12;
1312             salp1 = iv.salp1;
1313             calp1 = iv.calp1;
1314             salp2 = iv.salp2;
1315             calp2 = iv.calp2;
1316             var dnm = iv.dnm;
1317 
1318             if (sig12 >= 0) {
1319                 // short lines (inverseStart sets salp2, calp2, dnm)
1320                 s12x = sig12 * b * dnm;
1321                 m12x = GeoMath.sq(dnm) * b * Math.sin(sig12 / dnm);
1322                 if ((outmask & GeodesicMask.GEODESIC_SCALE) != 0) {
1323                     final var value = Math.cos(sig12 / dnm);
1324                     r.setScaleM12(value);
1325                     r.setScaleM21(value);
1326                 }
1327                 a12 = Math.toDegrees(sig12);
1328                 omg12 = lam12 / (f1 * dnm);
1329             } else {
1330                 // Newton's method. This is a straightforward solution of f(alp1) =
1331                 // lambda12(alp1) - lam12 = 0 with one wrinkle. f(alp) has exactly one
1332                 // root in the interval (0, pi) and its derivative is positive at the
1333                 // root. Thus f(alp) is positive for alp > alp1 and negative for alp < alp1.
1334                 // During the course of the iteration, a range (alp1a, alp1b) is maintained
1335                 // which brackets the root and with each evaluation of f(alp) the range is
1336                 // shrunk, if possible. Newton's method is restarted whenever the derivative
1337                 // of f is negative (because the new value of alp1 is then further from the
1338                 // solution) or if the new estimate of alp1 lies outside (0,pi); in this
1339                 // case, the new starting guess is taken to be (alp1a + alp1b) / 2.
1340                 var ssig1 = Double.NaN;
1341                 var csig1 = Double.NaN;
1342                 var ssig2 = Double.NaN;
1343                 var csig2 = Double.NaN;
1344                 var eps = Double.NaN;
1345                 var domg12 = Double.NaN;
1346                 var numit = 0;
1347                 // bracketing range
1348                 var salp1a = TINY;
1349                 var calp1a = 1.0;
1350                 var salp1b = TINY;
1351                 var calp1b = -1.0;
1352                 for (boolean tripn = false, tripb = false; numit < MAXIT2; ++numit) {
1353                     // the WGS84 test set: mean = 1.47, sd = 1.25, max = 16
1354                     // WGS84 and random input: mean = 2.85, sd = 0.60
1355                     final double v;
1356                     final double dv;
1357 
1358                     final var w = lambda12(sbet1, cbet1, dn1, sbet2, cbet2, dn2, salp1, calp1, slam12, clam12,
1359                             numit < MAXIT1, c1a, c2a, c3a);
1360                     v = w.lam12;
1361                     salp2 = w.salp2;
1362                     calp2 = w.calp2;
1363                     sig12 = w.sig12;
1364                     ssig1 = w.ssig1;
1365                     csig1 = w.csig1;
1366                     ssig2 = w.ssig2;
1367                     csig2 = w.csig2;
1368                     eps = w.eps;
1369                     domg12 = w.domg12;
1370                     dv = w.dlam12;
1371 
1372                     // 2 * TOL0 is approximately 1 ulp for a number in [0, pi].
1373                     // reversed test to allow escape with NaNs
1374                     final var tmp = (tripn ? 8 : 1);
1375                     if (tripb || !(Math.abs(v) >= tmp * TOL0)) {
1376                         break;
1377                     }
1378 
1379                     // update bracketing values
1380                     if (v > 0 && (numit > MAXIT1 || calp1 / salp1 > calp1b / salp1b)) {
1381                         salp1b = salp1;
1382                         calp1b = calp1;
1383                     } else if (v < 0 && (numit > MAXIT1 || calp1 / salp1 < calp1a / salp1a)) {
1384                         salp1a = salp1;
1385                         calp1a = calp1;
1386                     }
1387 
1388                     if (numit < MAXIT1 && dv > 0) {
1389                         final var dalp1 = -v / dv;
1390                         final var sdalp1 = Math.sin(dalp1);
1391                         final var cdalp1 = Math.cos(dalp1);
1392                         final var nsalp1 = salp1 * cdalp1 + calp1 * sdalp1;
1393                         if (nsalp1 > 0 && Math.abs(dalp1) < Math.PI) {
1394                             calp1 = calp1 * cdalp1 - salp1 * sdalp1;
1395                             salp1 = nsalp1;
1396 
1397                             p = GeoMath.norm(salp1, calp1);
1398                             salp1 = p.getFirst();
1399                             calp1 = p.getSecond();
1400 
1401                             // in some regimes we don't get quadratic convergence because
1402                             // slope -> 0. So use convergence conditions based on epsilon
1403                             // instead of sqrt(epsilon).
1404                             tripn = Math.abs(v) <= 16 * TOL0;
1405                             continue;
1406                         }
1407                     }
1408 
1409                     // either dv was not positive or updated value was outside legal range.
1410                     // Use the midpoint of the bracket as the next estimate.
1411                     // This mechanism is not needed for the WGS84 ellipsoid, but it does
1412                     // catch problems with more eccentric ellipsoids. Its efficacy is such
1413                     // for the WGS84 test set with the starting guess set to alp1 = 90deg
1414                     // the WGS84 test set: mean = 5.21, sd = 3.93, max = 24
1415                     // WGS84 and random input: mean = 4.74, sd = 0.99
1416                     salp1 = (salp1a + salp1b) / 2;
1417                     calp1 = (calp1a + calp1b) / 2;
1418 
1419                     p = GeoMath.norm(salp1, calp1);
1420                     salp1 = p.getFirst();
1421                     calp1 = p.getSecond();
1422 
1423                     tripn = false;
1424                     tripb = (Math.abs(salp1a - salp1) + (calp1a - calp1) < TOLB
1425                             || Math.abs(salp1 - salp1b) + (calp1 - calp1b) < TOLB);
1426                 }
1427 
1428                 // ensure that the reduced length and geodesic scale are computed in a
1429                 // "canonical" way, with the I2 integral.
1430                 final var lengthmask = outmask | ((outmask & (GeodesicMask.REDUCED_LENGTH
1431                         | GeodesicMask.GEODESIC_SCALE)) != 0 ? GeodesicMask.DISTANCE : GeodesicMask.NONE);
1432                 final var v = lengths(eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, lengthmask, c1a,
1433                         c2a);
1434                 s12x = v.s12b;
1435                 m12x = v.m12b;
1436                 if ((outmask & GeodesicMask.GEODESIC_SCALE) != 0) {
1437                     r.setScaleM12(v.m12);
1438                     r.setScaleM21(v.m21);
1439                 }
1440 
1441                 m12x *= b;
1442                 s12x *= b;
1443                 a12 = Math.toDegrees(sig12);
1444                 if ((outmask & GeodesicMask.AREA) != 0) {
1445                     // omg12 = lam12 - domg12
1446                     final var sdomg12 = Math.sin(domg12);
1447                     final var cdomg12 = Math.cos(domg12);
1448                     somg12 = slam12 * cdomg12 - clam12 * sdomg12;
1449                     comg12 = clam12 * cdomg12 + slam12 * sdomg12;
1450                 }
1451             }
1452         }
1453 
1454         if ((outmask & GeodesicMask.DISTANCE) != 0) {
1455             // convert -0 to 0
1456             r.setS12(0.0 + s12x);
1457         }
1458 
1459         if ((outmask & GeodesicMask.REDUCED_LENGTH) != 0) {
1460             // convert -0 to 0
1461             r.setM12(0.0 + m12x);
1462         }
1463 
1464         if ((outmask & GeodesicMask.AREA) != 0) {
1465             // from lambda12: sin(alp1) * cos(bet1) = sin(alp0)
1466             // calp0 > 0
1467             final var salp0 = salp1 * cbet1;
1468             final var calp0 = GeoMath.hypot(calp1, salp1 * sbet1);
1469             final double alp12;
1470             if (calp0 != 0 && salp0 != 0) {
1471                 // from lambda12: tan(bet) = tan(sig) * cos(alp)
1472                 // multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0).
1473                 var ssig1 = sbet1;
1474                 var csig1 = calp1 * cbet1;
1475                 var ssig2 = sbet2;
1476                 var csig2 = calp2 * cbet2;
1477                 final var k2 = GeoMath.sq(calp0) * ep2;
1478                 final var eps = k2 / (2 * (1 + Math.sqrt(1 + k2)) + k2);
1479                 final var a4 = GeoMath.sq(a) * calp0 * salp0 * e2;
1480 
1481                 p = GeoMath.norm(ssig1, csig1);
1482                 ssig1 = p.getFirst();
1483                 csig1 = p.getSecond();
1484 
1485                 p = GeoMath.norm(ssig2, csig2);
1486                 ssig2 = p.getFirst();
1487                 csig2 = p.getSecond();
1488 
1489                 final var c4a = new double[NC4];
1490                 c4f(eps, c4a);
1491                 final var b41 = sinCosSeries(false, ssig1, csig1, c4a);
1492                 final var b42 = sinCosSeries(false, ssig2, csig2, c4a);
1493                 r.setAreaS12(a4 * (b42 - b41));
1494             } else {
1495                 // avoid problems with indeterminate sig1, sig2 on equator
1496                 r.setAreaS12(0.0);
1497             }
1498 
1499             if (!meridian && somg12 > 1) {
1500                 somg12 = Math.sin(omg12);
1501                 comg12 = Math.cos(omg12);
1502             }
1503 
1504             // long difference not too big
1505             // lat difference not too big
1506             if (!meridian && comg12 > -0.7071 && sbet2 - sbet1 < 1.75) {
1507                 // use tan(gamma/2) = tan(omg12/2) * (tan(bet1/2)+tan(bet2/2))/
1508                 // (1+tan(bet1/2)*tan(bet2/2)) with tan(x/2) = sin(x)/(1 + cos(x))
1509                 final var domg12 = 1.0 + comg12;
1510                 final var dbet1 = 1.0 + cbet1;
1511                 final var dbet2 = 1.0 + cbet2;
1512                 alp12 = 2.0 * Math.atan2(somg12 * (sbet1 * dbet2 + sbet2 * dbet1),
1513                         domg12 * (sbet1 * sbet2 + dbet1 * dbet2));
1514             } else {
1515                 // alp12 = alp2 - alp1, used in atan2 so no need to normalize
1516                 var salp12 = salp2 * calp1 - calp2 * salp1;
1517                 var calp12 = calp2 * calp1 + salp2 * salp1;
1518 
1519                 // the right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz
1520                 // salp12 = -0 and alp12 = -180. However, this depends on the sign
1521                 // being attached to 0 correctly. The following ensures the correct
1522                 // behavior.
1523                 if (salp12 == 0.0 && calp12 < 0.0) {
1524                     salp12 = TINY * calp1;
1525                     calp12 = -1.0;
1526                 }
1527                 alp12 = Math.atan2(salp12, calp12);
1528             }
1529             r.setAreaS12(r.getAreaS12() + c2 * alp12);
1530             r.setAreaS12(r.getAreaS12() * swapp * lonsign * latsign);
1531 
1532             // convert -0 to 0
1533             r.setAreaS12(r.getAreaS12() + 0.0);
1534         }
1535 
1536         // convert calp, salp to azimuth accounting for lonsign, swapp, latsign
1537         if (swapp < 0) {
1538             var t = salp1;
1539             salp1 = salp2;
1540             salp2 = t;
1541 
1542             t = calp1;
1543             calp1 = calp2;
1544             calp2 = t;
1545 
1546             if ((outmask & GeodesicMask.GEODESIC_SCALE) != 0) {
1547                 t = r.getScaleM12();
1548                 r.setScaleM12(r.getScaleM21());
1549                 r.setScaleM21(t);
1550             }
1551         }
1552 
1553         salp1 *= swapp * lonsign;
1554         calp1 *= swapp * latsign;
1555         salp2 *= swapp * lonsign;
1556         calp2 *= swapp * latsign;
1557 
1558         // returned value in [0, 180]
1559         r.setA12(a12);
1560         result.salp1 = salp1;
1561         result.calp1 = calp1;
1562         result.salp2 = salp2;
1563         result.calp2 = calp2;
1564         return result;
1565     }
1566 
1567     /**
1568      * Safely creates an ellipsoid with.
1569      *
1570      * @param a equatorial radius (meters).
1571      * @param f flattening of ellipsoid. Setting <i>f</i> = 0 gives a sphere. Negative <i>f</i> gives a prolate
1572      *          ellipsoid.
1573      * @return a new Geodesic instance or null if something fails.
1574      */
1575     @SuppressWarnings("SameParameterValue")
1576     private static Geodesic safeInstance(final double a, final double f) {
1577         try {
1578             return new Geodesic(a, f);
1579         } catch (final GeodesicException e) {
1580             return null;
1581         }
1582     }
1583 
1584     private LengthsV lengths(
1585             final double eps, final double sig12, final double ssig1, final double csig1,
1586             final double dn1, final double ssig2, final double csig2, final double dn2,
1587             final double cbet1, final double cbet2,
1588             // scratch areas of the right size
1589             int outmask, final double[] c1a, final double[] c2a) {
1590         // return m12b = (reduced length)/mB; also calculate s12b = distance/mB,
1591         // and m0 = coefficient of secular term in expression for reduced length.
1592         outmask &= GeodesicMask.OUT_MASK;
1593 
1594         // to hold s12b, m12b, m0, M12, M21
1595         final var v = new LengthsV();
1596 
1597         var m0x = 0.0;
1598         var j12 = 0.0;
1599         var a1 = 0.0;
1600         var a2 = 0.0;
1601         if ((outmask & (GeodesicMask.DISTANCE | GeodesicMask.REDUCED_LENGTH | GeodesicMask.GEODESIC_SCALE)) != 0) {
1602             a1 = a1m1f(eps);
1603             c1f(eps, c1a);
1604             if ((outmask & (GeodesicMask.REDUCED_LENGTH | GeodesicMask.GEODESIC_SCALE)) != 0) {
1605                 a2 = a2m1f(eps);
1606                 c2f(eps, c2a);
1607                 m0x = a1 - a2;
1608                 a2 = 1 + a2;
1609             }
1610             a1 = 1 + a1;
1611         }
1612 
1613         if ((outmask & GeodesicMask.DISTANCE) != 0) {
1614             final var b1 = sinCosSeries(true, ssig2, csig2, c1a) - sinCosSeries(true, ssig1, csig1, c1a);
1615             // missing a factor of mB
1616             v.s12b = a1 * (sig12 + b1);
1617             if ((outmask & (GeodesicMask.REDUCED_LENGTH | GeodesicMask.GEODESIC_SCALE)) != 0) {
1618                 final var b2 = sinCosSeries(true, ssig2, csig2, c2a) - sinCosSeries(true, ssig1, csig1, c2a);
1619                 j12 = m0x * sig12 + (a1 * b1 - a2 * b2);
1620             }
1621         } else if ((outmask & (GeodesicMask.REDUCED_LENGTH | GeodesicMask.GEODESIC_SCALE)) != 0) {
1622             // assume here that NC1 >= NC2
1623             for (var l = 1; l <= NC2; ++l) {
1624                 c2a[l] = a1 * c1a[l] - a2 * c2a[l];
1625             }
1626             j12 = m0x * sig12 + (sinCosSeries(true, ssig2, csig2, c2a)
1627                     - sinCosSeries(true, ssig1, csig1, c2a));
1628         }
1629 
1630         if ((outmask & GeodesicMask.REDUCED_LENGTH) != 0) {
1631             v.m0 = m0x;
1632             // Missing a factor of mB.
1633             // Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure
1634             // accurate cancellation in the case of coincident points
1635             v.m12b = dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) - csig1 * csig2 * j12;
1636         }
1637 
1638         if ((outmask & GeodesicMask.GEODESIC_SCALE) != 0) {
1639             final var csig12 = csig1 * csig2 + ssig1 * ssig2;
1640             final var t = ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2);
1641             v.m12 = csig12 + (t * ssig2 - csig2 * j12) * ssig1 / dn1;
1642             v.m21 = csig12 - (t * ssig1 - csig1 * j12) * ssig2 / dn2;
1643         }
1644         return v;
1645     }
1646 
1647     private static double astroid(final double x, final double y) {
1648         // solve k^4 + 2 * k^3 - (x^2 + y^2 - 1) * k^2 - 2 * y^2 * k - y^2 = 0 for positive root k.
1649         // this solution is adapted from Geocentric.reverse
1650         final double k;
1651         final var p = GeoMath.sq(x);
1652         //noinspection all
1653         final var q = GeoMath.sq(y);
1654         final var r = (p + q - 1) / 6;
1655 
1656         if (!(q == 0 && r <= 0)) {
1657             // avoid possible division by zero when r = 0 by multiplying equations for s and t by
1658             // r^3 and r, resp.
1659 
1660             // s = r^3 * s
1661             final var s = p * q / 4;
1662             final var r2 = GeoMath.sq(r);
1663             final var r3 = r * r2;
1664             // the discriminant of the quadratic equation for T3. This is zero on the
1665             // evolute curve p^(1/3) + q^(1/3) = 1
1666             final var disc = s * (s + 2 * r3);
1667             var u = r;
1668             if (disc >= 0) {
1669                 var t3 = s + r3;
1670 
1671                 // pick the sign on the sqrt to maximize abs(t3). This minimizes loss of precision
1672                 // due to cancellation. The result is unchanged because of the way the T is used in
1673                 // definition of u.
1674 
1675                 // t3 = (r * t)^3
1676                 t3 += t3 < 0 ? -Math.sqrt(disc) : Math.sqrt(disc);
1677 
1678                 // N.B. cbrt always returns the double root. cbrt(-8) = -2.
1679                 // t = r * t
1680                 final var t = GeoMath.cbrt(t3);
1681 
1682                 // t can be zero; but then r2 / t -> 0.
1683                 u += t + (t != 0 ? r2 / t : 0);
1684             } else {
1685                 // t is complex, but the way u is defined the result is double.
1686                 final var ang = Math.atan2(Math.sqrt(-disc), -(s + r3));
1687                 // there are three possible cube roots. We choose the root which
1688                 // avoids cancellation. Note that disc < 0 implies that r < 0.
1689                 u += 2 * r * Math.cos(ang / 3);
1690             }
1691 
1692             // guaranteed positive
1693             final var v = Math.sqrt(GeoMath.sq(u) + q);
1694             // avoid loss of accuracy when u < 0
1695             // u + v, guaranteed positive
1696             final var uv = u < 0 ? q / (v - u) : u + v;
1697             // positive?
1698             final var w = (uv - q) / (2 * v);
1699 
1700             // rearrange expression for k to avoid loss of accuracy due to subtraction. Division
1701             // by 0 not possible because uv > 0, w >= 0
1702 
1703             // guaranteed positive
1704             k = uv / (Math.sqrt(uv + GeoMath.sq(w)) + w);
1705         } else {
1706             // y = 0 with |x| <= 1. Handle this case directly
1707             // for y small, positive root is k = abs(y) / sqrt(1 - x ^2)
1708             k = 0.0;
1709         }
1710         return k;
1711     }
1712 
1713     private InverseStartV inverseStart(
1714             final double sbet1, final double cbet1, final double dn1,
1715             final double sbet2, final double cbet2, final double dn2,
1716             final double lam12, final double slam12, final double clam12,
1717             // scratch areas of the right size
1718             final double[] c1a, final double[] c2a) {
1719         // return a starting point for Newton's method in salp1 and calp1 (function value is -1).
1720         // If Newton's method doesn't need to be used, return also salp2 and calp2 and function
1721         // value is sig12.
1722 
1723         // to hold sig12, salp1, calp1, salp2, calp2, dnm.
1724         final var w = new InverseStartV();
1725 
1726         // return value
1727         w.sig12 = -1;
1728 
1729         // bet12 = bet2 - bet1 in [0, pi); bet12a = bet2 + bet1 in (-pi, 0]
1730         final var sbet12 = sbet2 * cbet1 - cbet2 * sbet1;
1731         final var cbet12 = cbet2 * cbet1 + sbet2 * sbet1;
1732         final var sbet12a = sbet2 * cbet1 + cbet2 * sbet1;
1733         final var shortline = cbet12 >= 0 && sbet12 < 0.5 && cbet2 * lam12 < 0.5;
1734         double somg12;
1735         double comg12;
1736         if (shortline) {
1737             var sbetm2 = GeoMath.sq(sbet1 + sbet2);
1738 
1739             // sin((bet1 + bet2) / 2)^2
1740             // = (sbet1 + sbet2)^2 / ((sbet1 + sbet2)^2 + (cbet1 + cbet2)^2)
1741             sbetm2 /= sbetm2 + GeoMath.sq(cbet1 + cbet2);
1742             w.dnm = Math.sqrt(1 + ep2 * sbetm2);
1743             final var omg12 = lam12 / (f1 * w.dnm);
1744             somg12 = Math.sin(omg12);
1745             comg12 = Math.cos(omg12);
1746         } else {
1747             somg12 = slam12;
1748             comg12 = clam12;
1749         }
1750 
1751         w.salp1 = cbet2 * somg12;
1752         w.calp1 = comg12 >= 0.0
1753                 ? sbet12 + cbet2 * sbet1 * GeoMath.sq(somg12) / (1.0 + comg12)
1754                 : sbet12a - cbet2 * sbet1 * GeoMath.sq(somg12) / (1.0 - comg12);
1755 
1756         final var ssig12 = GeoMath.hypot(w.salp1, w.calp1);
1757         final var csig12 = sbet1 * sbet2 + cbet1 * cbet2 * comg12;
1758 
1759         if (shortline && ssig12 < etol2) {
1760             // really short lines
1761             w.salp2 = cbet1 * somg12;
1762             w.calp2 = sbet12 - cbet1 * sbet2 * (comg12 >= 0.0 ? GeoMath.sq(somg12) / (1.0 + comg12) : 1.0 - comg12);
1763 
1764             final var p = GeoMath.norm(w.salp2, w.calp2);
1765             w.salp2 = p.getFirst();
1766             w.calp2 = p.getSecond();
1767 
1768             // set return value
1769             w.sig12 = Math.atan2(ssig12, csig12);
1770             // skip astroid calc if too eccentric
1771         } else if (!(Math.abs(n) > 0.1 || csig12 >= 0 || ssig12 >= 6 * Math.abs(n) * Math.PI * GeoMath.sq(cbet1))) {
1772             // scale lam12 and bet2 to x, y coordinate system where antipodal point is at origin and
1773             // singular point is at y = 0, x = -1
1774             final double y;
1775             final double lamscale;
1776             final double betscale;
1777 
1778             // In C++ volatile declaration needed to fix inverse case
1779             // 56.320923501171 0 -56.320923501171 179.664747671772880215
1780             final double x;
1781             // lam12 - pi
1782             final var lam12x = Math.atan2(-slam12, -clam12);
1783             if (f >= 0) {
1784                 // in fact f == 0 does not get here
1785 
1786                 // x = dlong, y = dlat
1787                 final var k2 = GeoMath.sq(sbet1) * ep2;
1788                 final var eps = k2 / (2 * (1 + Math.sqrt(1 + k2)) + k2);
1789                 lamscale = f * cbet1 * a3f(eps) * Math.PI;
1790 
1791                 betscale = lamscale * cbet1;
1792 
1793                 x = lam12x / lamscale;
1794                 y = sbet12a / betscale;
1795             } else {
1796                 // mF < 0
1797                 // x = dlat, y = dlong
1798                 final var cbet12a = cbet2 * cbet1 - sbet2 * sbet1;
1799                 final var bet12a = Math.atan2(sbet12a, cbet12a);
1800                 final double m12b;
1801                 final double m0;
1802 
1803                 // in the case of lon12 = 180, this repeats a calculation made in inverse
1804                 final var v = lengths(n, Math.PI + bet12a, sbet1, -cbet1, dn1, sbet2, cbet2, dn2, cbet1, cbet2,
1805                         GeodesicMask.REDUCED_LENGTH, c1a, c2a);
1806                 m12b = v.m12b;
1807                 m0 = v.m0;
1808 
1809                 x = -1 + m12b / (cbet1 * cbet2 * m0 * Math.PI);
1810                 betscale = x < -0.01 ? sbet12a / x : -f * GeoMath.sq(cbet1) * Math.PI;
1811                 lamscale = betscale / cbet1;
1812                 y = lam12x / lamscale;
1813             }
1814 
1815             if (y > -TOL1 && x > -1 - XTHRESH) {
1816                 // strip near cut
1817                 if (f >= 0) {
1818                     w.salp1 = Math.min(1.0, -x);
1819                     w.calp1 = -Math.sqrt(1.0 - GeoMath.sq(w.salp1));
1820                 } else {
1821                     w.calp1 = Math.max(x > -TOL1 ? 0.0 : -1.0, x);
1822                     w.salp1 = Math.sqrt(1.0 - GeoMath.sq(w.calp1));
1823                 }
1824             } else {
1825                 // estimate alp1, by solving the astroid problem
1826 
1827                 // could estimate alpha1 = theta + pi/2, directly, i.e.,
1828                 // calp1 = y / k; salp1 = -x / (1 + k); for mF >= 0
1829                 // calp1 = x / (1 + k); salp1 = -y / k; for mF < 0 (need to check)
1830 
1831                 // However, it's better to estimate omg12 from astroid and use spherical
1832                 // formula to compute alp1. This reduces the mean number of Newton iterations
1833                 // for astroid cases from 2.24 (min 0, max 6) to 2.12 (min 0 max 5). The changes
1834                 // in the number of iterations are as follows:
1835 
1836                 // change    percent
1837                 //   1           5
1838                 //   0           78
1839                 //  -1          16
1840                 //  -2          0.6
1841                 //  -3          0.04
1842                 //  -4          0.002
1843 
1844                 // The histogram of iterations is (m = number of iterations estimating alp1 directly,
1845                 // n = number of iterations estimating via omg12, total number of trials = 148605):
1846 
1847                 // iter      m       n
1848                 //   0       148     186
1849                 //   1       13046   13845
1850                 //   2       93315   102225
1851                 //   3       36189   32341
1852                 //   4       5396    7
1853                 //   5       455     1
1854                 //   6       56      0
1855 
1856                 // Because omg12 is near pi, estimate work with omg12a = pi - omg12
1857                 final var k = astroid(x, y);
1858                 final var omg12a = lamscale * (f >= 0.0 ? -x * k / (1.0 + k) : -y * (1 + k) / k);
1859                 somg12 = Math.sin(omg12a);
1860                 comg12 = -Math.cos(omg12a);
1861 
1862                 // update spherical estimate of alp1 using omg12 instead of lam12
1863                 w.salp1 = cbet2 * somg12;
1864                 w.calp1 = sbet12a - cbet2 * sbet1 * GeoMath.sq(somg12) / (1.0 - comg12);
1865             }
1866         }
1867 
1868         // sanity check on starting guess. Backwards check allows NaN through
1869         if (!(w.salp1 <= 0.0)) {
1870             final var p = GeoMath.norm(w.salp1, w.calp1);
1871             w.salp1 = p.getFirst();
1872             w.calp1 = p.getSecond();
1873         } else {
1874             w.salp1 = 1;
1875             w.calp1 = 0;
1876         }
1877         return w;
1878     }
1879 
1880     private Lambda12V lambda12(
1881             final double sbet1, final double cbet1, final double dn1,
1882             final double sbet2, final double cbet2, final double dn2,
1883             final double salp1, double calp1,
1884             final double slam120, final double clam120, final boolean diffp,
1885             // scratch areas of the right size
1886             final double[] c1a, final double[] c2a, final double[] c3a) {
1887         // object to hold lam12, salp2, calp2, sig12, ssig1, csig1, ssig2, csig2, eps, domg12, dlam12
1888         final var w = new Lambda12V();
1889 
1890         if (sbet1 == 0 && calp1 == 0) {
1891             // break degeneracy of equatorial line. This case has already been handled
1892             calp1 = -TINY;
1893         }
1894 
1895         // sin(alp1) * cos(bet1) = sin(alp0)
1896         final var salp0 = salp1 * cbet1;
1897         // calp0 > 0
1898         final var calp0 = GeoMath.hypot(calp1, salp1 * sbet1);
1899 
1900         double somg1;
1901         double comg1;
1902         double somg2;
1903         double comg2;
1904         double somg12;
1905         double comg12;
1906         // tan(bet1) = tan(sig1) * cos(alp1)
1907         // tan(omg1) = sin(alp0) * tan(sig1) = tan(omg1) = tan(alp1) * sin(bet1)
1908         w.ssig1 = sbet1;
1909         somg1 = salp0 * sbet1;
1910         w.csig1 = comg1 = calp1 * cbet1;
1911 
1912         var p = GeoMath.norm(w.ssig1, w.csig1);
1913         w.ssig1 = p.getFirst();
1914         w.csig1 = p.getSecond();
1915 
1916         // GeoMath.norm(somg1, comg1); -- don't need to normalize!
1917 
1918         // Enforce symmetries in the case abs(bet2) = -bet1. Need to be careful about this case,
1919         // since this can yield singularities in the Newton iteration.
1920         // sin(alp2) * cos(bet2) = sin(alp0)
1921         w.salp2 = cbet2 != cbet1 ? salp0 / cbet2 : salp1;
1922         // calp2 = sqrt(1 - sq(salp2))
1923         //       = sqrt(sq(calp0) - sq(sbet2)) / cbet2
1924         // and subst for calp0 and rearrange to give (choose positive sqrt
1925         // to give alp2 in [0, pi/2]).
1926         final var tmp = (cbet1 < -sbet1 ? (cbet2 - cbet1) * (cbet1 + cbet2) : (sbet1 - sbet2) * (sbet1 + sbet2));
1927         w.calp2 = cbet2 != cbet1 || Math.abs(sbet2) != -sbet1
1928                 ? Math.sqrt(GeoMath.sq(calp1 * cbet1) + tmp) / cbet2 : Math.abs(calp1);
1929         // tan(bet2) = tan(sig2) * cos(alp2)
1930         // tan(omg2) = sin(alp0) * tan(sig2)
1931         w.ssig2 = sbet2;
1932         somg2 = salp0 * sbet2;
1933         w.csig2 = comg2 = w.calp2 * cbet2;
1934 
1935         p = GeoMath.norm(w.ssig2, w.csig2);
1936         w.ssig2 = p.getFirst();
1937         w.csig2 = p.getSecond();
1938 
1939         // GeoMath.norm(somg2, comg2); -- don't need to normalize!
1940 
1941         // sig12 = sig2 - sig1, limit to [0, pi]
1942         w.sig12 = Math.atan2(Math.max(0.0, w.csig1 * w.ssig2 - w.ssig1 * w.csig2),
1943                 w.csig1 * w.csig2 + w.ssig1 * w.ssig2);
1944 
1945         // omg12 = omg2 - omg1, limit to [0, pi]
1946         somg12 = Math.max(0.0, comg1 * somg2 - somg1 * comg2);
1947         comg12 = comg1 * comg2 + somg1 * somg2;
1948         // eta = omg12 - lam120
1949         final var eta = Math.atan2(somg12 * clam120 - comg12 * slam120, comg12 * clam120 + somg12 * slam120);
1950 
1951         final var k2 = GeoMath.sq(calp0) * ep2;
1952         w.eps = k2 / (2 * (1 + Math.sqrt(1 + k2)) + k2);
1953         c3f(w.eps, c3a);
1954         final var b312 = (sinCosSeries(true, w.ssig2, w.csig2, c3a)
1955                 - sinCosSeries(true, w.ssig1, w.csig1, c3a));
1956         w.domg12 = -f * a3f(w.eps) * salp0 * (w.sig12 + b312);
1957         w.lam12 = eta + w.domg12;
1958 
1959         if (diffp) {
1960             if (w.calp2 == 0) {
1961                 w.dlam12 = -2 * f1 * dn1 / sbet1;
1962             } else {
1963                 final var v = lengths(w.eps, w.sig12, w.ssig1, w.csig1, dn1, w.ssig2, w.csig2, dn2, cbet1, cbet2,
1964                         GeodesicMask.REDUCED_LENGTH, c1a, c2a);
1965                 w.dlam12 = v.m12b;
1966                 w.dlam12 *= f1 / (w.calp2 * cbet2);
1967             }
1968         }
1969 
1970         return w;
1971     }
1972 
1973     private static class Lambda12V {
1974         private double lam12;
1975         private double salp2;
1976         private double calp2;
1977         private double sig12;
1978         private double ssig1;
1979         private double csig1;
1980         private double ssig2;
1981         private double csig2;
1982         private double eps;
1983         private double domg12;
1984         private double dlam12;
1985 
1986         private Lambda12V() {
1987             lam12 = salp2 = calp2 = sig12 = ssig1 = csig1 = ssig2 = csig2 = eps = domg12 = dlam12 = Double.NaN;
1988         }
1989     }
1990 
1991     private static class InverseStartV {
1992         private double sig12;
1993         private double salp1;
1994         private double calp1;
1995 
1996         // only updated if return value >= 0
1997         private double salp2;
1998         private double calp2;
1999 
2000         // only updated for short lines
2001         private double dnm;
2002 
2003         private InverseStartV() {
2004             sig12 = salp1 = calp1 = salp2 = calp2 = dnm = Double.NaN;
2005         }
2006     }
2007 
2008     private static class LengthsV {
2009         private double s12b;
2010         private double m12b;
2011         private double m0;
2012         private double m12;
2013         private double m21;
2014 
2015         private LengthsV() {
2016             s12b = m12b = m0 = m12 = m21 = Double.NaN;
2017         }
2018     }
2019 
2020     private static class InverseData {
2021         private final GeodesicData g;
2022         private double salp1;
2023         private double calp1;
2024         private double salp2;
2025         private double calp2;
2026 
2027         private InverseData() {
2028             g = new GeodesicData();
2029             salp1 = calp1 = salp2 = calp2 = Double.NaN;
2030         }
2031     }
2032 }