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   * A geodesic line
20   * GeodesicLine facilitates the determination of a series of points on a single geodesic. The
21   * starting point (<i>lat1</i>, <i>lon1</i>) and the azimuth <i>azi1</i> are specified in the
22   * constructor; alternatively, the {@link Geodesic#line} method can be used to create a GeodesicLine.
23   * {@link #position} returns the location of point 2 a distance <i>s12</i> along the geodesic.
24   * Alternatively {@link #arcPosition} gives the position of point 2 an arc length <i>a12</i> along
25   * the geodesic.
26   * You can register the position of a reference point 3 a distance (arc length), <i>s13</i>
27   * (<i>a13</i>) along the geodesic with the {@link #setDistance} ({@link #setArc}) functions. Points
28   * a fractional distance along the line can be found by providing, for example, 0.5 * {@link #getDistance}
29   * as an argument to {@link #position}. The {@link Geodesic#inverseLine} or {@link Geodesic#directLine}
30   * methods return GeodesicLine objects with point 3 set to the point 2 of the corresponding geodesic
31   * problem. GeodesicLine objects created with the public constructor or with {@link Geodesic#line}
32   * have <i>s13</i> and <i>a13</i> set to NaNs.
33   * The calculations are accurate to better than 15 nm (15 nanometers). See Sec. 9 of
34   * <a href="https://arxiv.org/abs/1102.1215v1">arXiv:1102.1215v1</a> for details. The algorithms used
35   * by this class are based on series expansions using the flattening <i>f</i> as a small parameter.
36   * These are only accurate for |<i>f</i> &lt; 0.02; however reasonably accurate results will be
37   * obtained for |<i>f</i> &lt; 0.2.
38   * The algorithms are described in
39   * <ul>
40   *     <li>
41   *         C. F. F. Karney, <a href="https://doi.org/10.1007/s00190-012-0578-z">
42   *         Algorithms for geodesics</a>,
43   *         J. Geodesy <b>87</b>, 43&ndash;55 (2013)
44   *         (<a href="https://geographiclib.sourceforge.io/geod-addenda.html">addenda</a>).
45   *     </li>
46   * </ul>
47   * Here's an example of using this class
48   * <pre>
49   * {@code
50   * import net.sf.geographiclib.*;
51   * public class GeodesicLineTest {
52   *   public static void main(String[] args) {
53   *     // Print waypoints between JFK and SIN
54   *     Geodesic geod = Geodesic.WGS84;
55   *     double
56   *       lat1 = 40.640, lon1 = -73.779, // JFK
57   *       lat2 =  1.359, lon2 = 103.989; // SIN
58   *     GeodesicLine line = geod.InverseLine(lat1, lon1, lat2, lon2,
59   *                                          GeodesicMask.DISTANCE_IN |
60   *                                          GeodesicMask.LATITUDE |
61   *                                          GeodesicMask.LONGITUDE);
62   *     double ds0 = 500e3;     // Nominal distance between points = 500 km
63   *     // The number of intervals
64   *     int num = (int)(Math.ceil(line.Distance() / ds0));
65   *     {
66   *       // Use intervals of equal length
67   *       double ds = line.Distance() / num;
68   *       for (int i = 0; i <= num; ++i) {
69   *         GeodesicData g = line.Position(i * ds,
70   *                                        GeodesicMask.LATITUDE |
71   *                                        GeodesicMask.LONGITUDE);
72   *         System.out.println(i + " " + g.lat2 + " " + g.lon2);
73   *       }
74   *     }
75   *     {
76   *       // Slightly faster, use intervals of equal arc length
77   *       double da = line.Arc() / num;
78   *       for (int i = 0; i <= num; ++i) {
79   *         GeodesicData g = line.ArcPosition(i * da,
80   *                                           GeodesicMask.LATITUDE |
81   *                                           GeodesicMask.LONGITUDE);
82   *         System.out.println(i + " " + g.lat2 + " " + g.lon2);
83   *       }
84   *     }
85   *   }
86   * }}</pre>
87   */
88  public class GeodesicLine {
89  
90      private static final int NC1 = Geodesic.NC1;
91      private static final int NC1P = Geodesic.NC1P;
92      private static final int NC2 = Geodesic.NC2;
93      private static final int NC3 = Geodesic.NC3;
94      private static final int NC4 = Geodesic.NC4;
95  
96      private double lat1;
97      private double lon1;
98      private double azi1;
99  
100     private double a;
101     private double f;
102     private double b;
103     private double c2;
104     private double f1;
105     private double salp0;
106     private double calp0;
107     private double k2;
108     private double salp1;
109     private double calp1;
110     private double ssig1;
111     private double csig1;
112     private double dn1;
113     private double stau1;
114     private double ctau1;
115     private double somg1;
116     private double comg1;
117     private double a1m1;
118     private double a2m1;
119     private double a3c;
120     private double b11;
121     private double b21;
122     private double b31;
123     private double a4;
124     private double b41;
125 
126     private double a13;
127     private double s13;
128 
129     // index zero elements of mC1a, mC1pa, mC2a, mC3a are unused
130     // all the elements of mC4a are used.
131     private double[] c1a;
132     private double[] c1pa;
133     private double[] c2a;
134     private double[] c3a;
135     private double[] c4a;
136 
137     private int caps;
138 
139     /**
140      * Constructor for a geodesic line staring at latitude <i>lat1</i>, longitude <i>lon1</i>,
141      * and azimuth <i>azi1</i> (all in degrees).
142      * If the point is at a pole, the azimuth is defined by keeping <i>lon1</i> fixed, writing
143      * <i>lat1</i> = &plusmn;(90&deg; &minus; &epsilon;), and taking the limit &epsilon;
144      * &rarr; 0+.
145      *
146      * @param g    a {@link Geodesic} object used to compute the necessary information about the
147      *             GeodesicLine
148      * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range [&minus;90&deg;, 90&deg;].
149      * @param lon1 longitude of point 1 (degrees).
150      * @param azi1 azimuth at point 1 (degrees).
151      */
152     public GeodesicLine(final Geodesic g, final double lat1, final double lon1, final double azi1) {
153         this(g, lat1, lon1, azi1, GeodesicMask.ALL);
154     }
155 
156     /**
157      * Constructor for a geodesic line starting at latitude <i>lat1</i>, longitude <i>lon1</i>,
158      * and azimuth <i>azi1</i> (all in degrees) with a subset of the capabilities included.
159      * The {@link GeodesicMask} values are:
160      * <ul>
161      *     <li>
162      *         <i>caps</i> |= {@link GeodesicMask#LATITUDE} for the latitude <i>lat2</i>; this is
163      *         added automatically
164      *     </li>
165      *     <li>
166      *         <i>caps</i> |= {@link GeodesicMask#LONGITUDE} for the longitude <i>lon2</i>
167      *     </li>
168      *     <li>
169      *         <i>caps</i> |= {@link GeodesicMask#AZIMUTH} for the azimuth <i>azi2</i>; this is
170      *         added automatically
171      *     </li>
172      *     <li>
173      *         <i>caps</i> |= {@link GeodesicMask#DISTANCE} for the distance <i>s12</i>
174      *     </li>
175      *     <li>
176      *         <i>caps</i> |= {@link GeodesicMask#REDUCED_LENGTH} for the reduced length <i>m12</i>
177      *     </li>
178      *     <li>
179      *         <i>caps</i> |= {@link GeodesicMask#GEODESIC_SCALE} for the geodesic scales <i>M12</i>
180      *         and <i>M21</i>
181      *     </li>
182      *     <li>
183      *         <i>caps</i> |= {@link GeodesicMask#AREA} for the area <i>S12</i>
184      *     </li>
185      *     <li>
186      *         <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN} permits the length of the geodesic
187      *         to be given in terms of <i>s12</i>; without this capability the length can only be
188      *         specified in terms of arc length
189      *     </li>
190      *     <li>
191      *         <i>caps</i> |= {@link GeodesicMask#ALL} for all of the above.
192      *     </li>
193      * </ul>
194      *
195      * @param g    a {@link Geodesic} object used to compute the necessary information about the
196      *             GeodesicLine.
197      * @param lat1 latitude of point 1 (degrees).
198      * @param lon1 longitude of point 1 (degrees).
199      * @param azi1 azimuth at point 1 (degrees).
200      * @param caps bitor'ed combination of {@link GeodesicMask} values specifying the capabilities
201      *             the GeodesicLine object should possess, i.e., which quantities can be returned in
202      *             calls to {@link #position}.
203      */
204     public GeodesicLine(final Geodesic g, final double lat1, final double lon1, double azi1, final int caps) {
205         azi1 = GeoMath.angNormalize(azi1);
206 
207         final var p = GeoMath.sincosd(GeoMath.angRound(azi1));
208         final var pSalp1 = p.getFirst();
209         final var pCalp1 = p.getSecond();
210 
211         lineInit(g, lat1, lon1, azi1, pSalp1, pCalp1, caps);
212     }
213 
214     protected GeodesicLine(
215             final Geodesic g, final double lat1, final double lon1, final double azi1,
216             final double salp1, final double calp1, final int caps, final boolean arcmode, final double s13A13) {
217         lineInit(g, lat1, lon1, azi1, salp1, calp1, caps);
218         genSetDistance(arcmode, s13A13);
219     }
220 
221     /**
222      * Compute the position of point 2 which is a distance <i>s12</i> (meters) from point 1.
223      * The values of <i>lon2</i> and <i>azi2</i> returned are in the range [&minus;180&deg;, 180&deg;].
224      * The GeodesicLine object <i>must</i> have been constructed with <i>caps</i>
225      * |= {@link GeodesicMask#DISTANCE_IN}; otherwise no parameters are set.
226      *
227      * @param s12 distance from point 1 to point 2 (meters); it can be negative.
228      * @return a {@link GeodesicData} object with the following fields: <i>lat1</i>, <i>lon1</i>,
229      * <i>azi1</i>, <i>lat2</i>, <i>lon2</i>, <i>azi2</i>, <i>s12</i>, <i>a12</i>. Some of these
230      * results may be missing if the GeodesicLine did not include the relevant capability.
231      */
232     public GeodesicData position(final double s12) {
233         return position(false, s12, GeodesicMask.STANDARD);
234     }
235 
236     /**
237      * Compute the position of point 2 which is a distance <i>s12</i> (meters) from point 1 and
238      * with a subset of the geodesic results returned.
239      * The GeodesicLine object <i>must</i> have been constructed with <i>caps</i> |=
240      * {@link GeodesicMask#DISTANCE_IN}; otherwise no parameters are set.
241      * Requesting a value which the GeodesicLine object is not capable of computing is not an error
242      * (no parameters will be set). The value of <i>lon2</i> returned is normally in the range
243      * [&minus;180&deg;, 180&deg;]; however if the <i>outmask</i> includes the
244      * {@link GeodesicMask#LONG_UNROLL} flag, the longitude is "unrolled" so that the quantity
245      * <i>lon2</i> &minus; <i>lon1</i> indicates how many times and in what sense the geodesic
246      * encircles the ellipsoid.
247      *
248      * @param s12     distance from point 1 to point 2 (meters); it can be negative.
249      * @param outmask a bitor'ed combination of {@link GeodesicMask} values specifying which results
250      *                should be returned.
251      * @return a {@link GeodesicData} object including the requested results.
252      */
253     public GeodesicData position(final double s12, final int outmask) {
254         return position(false, s12, outmask);
255     }
256 
257     /**
258      * Compute the position of point 2 which is an arc length <i>a12</i> (degrees) from point 1.
259      * The values of <i>lon2</i> and <i>azi2</i> returned are in the range [&minus;180&deg;, 180&deg;].
260      * The GeodesicLine object <i>must</i> have been constructed with <i>caps</i> |=
261      * {@link GeodesicMask#DISTANCE_IN}; otherwise no parameters are set.
262      *
263      * @param a12 arc length from point 1 to point 2 (degrees); it can be negative.
264      * @return a {@link GeodesicData} object with the following fields: <i>lat1</i>, <i>lon1</i>,
265      * <i>azi1</i>, <i>lat2</i>, <i>lon2</i>, <i>azi2</i>, <i>s12</i>, <i>a12</i>. Some of these
266      * results may be missing if the GeodesicLine did not include the relevant capability.
267      */
268     public GeodesicData arcPosition(final double a12) {
269         return position(true, a12, GeodesicMask.STANDARD);
270     }
271 
272     /**
273      * Compute the position of point 2 which is an arc length <i>a12</i> (degrees) from point 1 and
274      * with a subset of th geodesic results returned.
275      * Requesting a value which the GeodesicLine object is not capable of computing is not an error
276      * (no parameters will be set). The value of <i>lon2</i> returned is in the range
277      * [&minus;180&deg;, 180&deg;], unless the <i>outmask</i> includes the
278      * {@link GeodesicMask#LONG_UNROLL} flags.
279      *
280      * @param a12     arc length from point 1 to point 2 (degrees); it can be negative.
281      * @param outmask a bitor'ed combination of {@link GeodesicMask} values specifying which results
282      *                should be returned.
283      * @return a {@link GeodesicData} object giving <i>lat1</i>, <i>lon2</i>, <i>azi2</i>, and
284      * <i>a12</i>.
285      */
286     public GeodesicData arcPosition(final double a12, final int outmask) {
287         return position(true, a12, outmask);
288     }
289 
290     /**
291      * The general position function. {@link #position(double, int)} and {@link #arcPosition(double, int)}
292      * are defined in terms of this function.
293      * The {@link GeodesicMask} values possible for <i>outmask</i> are
294      * <ul>
295      *     <li>
296      *         <i>outmask</i> |= {@link GeodesicMask#LATITUDE} for the latitude <i>lat2</i>;
297      *     </li>
298      *     <li>
299      *         <i>outmask</i> |= {@link GeodesicMask#LONGITUDE} for the latitude <i>lon2</i>;
300      *     </li>
301      *     <li>
302      *         <i>outmask</i> |= {@link GeodesicMask#AZIMUTH} for the latitude <i>azi2</i>;
303      *     </li>
304      *     <li>
305      *         <i>outmask</i> |= {@link GeodesicMask#DISTANCE} for the distance <i>s12</i>;
306      *     </li>
307      *     <li>
308      *         <i>outmask</i> |= {@link GeodesicMask#REDUCED_LENGTH} for the reduced length
309      *         <i>m12</i>;
310      *     </li>
311      *     <li>
312      *         <i>outmask</i> |= {@link GeodesicMask#GEODESIC_SCALE} for the geodesic scales
313      *         <i>M12</i> and <i>M21</i>;
314      *     </li>
315      *     <li>
316      *         <i>outmask</i> |= {@link GeodesicMask#ALL} for all of the above;
317      *     </li>
318      *     <li>
319      *         <i>outmask</i> |= {@link GeodesicMask#LONG_UNROLL} to unroll <i>lon2</i> (instead of
320      *         reducing it to the range [&minus;180&deg;, 180&deg;]).
321      *     </li>
322      * </ul>
323      *
324      * @param arcmode boolean flag determining the meaning of the second parameter; if arcmode is false,
325      *                then the GeodesicLine object must have been constructed with <i>caps</i> |=
326      *                {@link GeodesicMask#DISTANCE_IN}.
327      * @param s12A12  if <i>arcmode</i> is false, this is the distance between point 1 and point 2 (meters);
328      *                otherwise it is the arc length between point 1 and point 2 (degrees); it can be
329      *                negative.
330      * @param outmask a bitor'ed combination of {@link GeodesicMask} values specifying which results
331      *                should be returned.
332      * @return a {@link GeodesicData} object with the requested results. Requesting a value which the
333      * GeodesicLine object is not capable of computing is not an error; Double.NaN is returned instead.
334      */
335     public GeodesicData position(final boolean arcmode, final double s12A12, int outmask) {
336         outmask &= caps & GeodesicMask.OUT_MASK;
337         final var r = new GeodesicData();
338         if (!(init() && (arcmode || (caps & (GeodesicMask.OUT_MASK & GeodesicMask.DISTANCE_IN)) != 0))) {
339             // uninitialized or impossible distance calculation requested
340             return r;
341         }
342 
343         r.setLat1(lat1);
344         r.setAzi1(azi1);
345         r.setLon1(((outmask & GeodesicMask.LONG_UNROLL) != 0) ? lon1 : GeoMath.angNormalize(lon1));
346 
347         // avoid warning about uninitialized b12
348         double sig12;
349         double ssig12;
350         double csig12;
351         var b12 = 0.0;
352         var ab1 = 0.0;
353         if (arcmode) {
354             // interpret s12A12 as spherical arc length
355             r.setA12(s12A12);
356             sig12 = Math.toRadians(s12A12);
357 
358             final var p = GeoMath.sincosd(s12A12);
359             ssig12 = p.getFirst();
360             csig12 = p.getSecond();
361         } else {
362             // interpret s12A12 as distance
363             r.setS12(s12A12);
364 
365             final var tau12 = s12A12 / (b * (1 + a1m1));
366             final var s = Math.sin(tau12);
367             final var c = Math.cos(tau12);
368 
369             // tau2 = tau1 + tau12
370             b12 = -Geodesic.sinCosSeries(true, stau1 * c + ctau1 * s, ctau1 * c - stau1 * s, c1pa);
371             sig12 = tau12 - (b12 - b11);
372             ssig12 = Math.sin(sig12);
373             csig12 = Math.cos(sig12);
374 
375             if (Math.abs(f) > 0.01) {
376                 // reverted distance series is inaccurate for |f| > 1/100, so correct sig12 with 1
377                 // Newton iteration. The following table shows the approximate maximum error for
378                 // a = WGSa() and various f relative to GeodesicExact.
379                 // erri = the error in the inverse solution (nm)
380                 // errd = the error in the direct solution (series only) (nm)
381                 // errda = the error in the direct solution (series + 1 Newton) (nm)
382 
383                 //  f       erri        errd        errda
384                 //  -1/5    12e6        1.2e9       69e6
385                 //  -1/10   123e3       12e6        765e3
386                 //  -1/20   1110        108e3       7155
387                 //  -1/50   18.63       200.9       27.12
388                 //  -1/100  18.63       23.78       23.37
389                 //  -1/150  18.63       21.05       20.26
390                 //  1/150   22.35       24.73       25.83
391                 //  1/100   22.35       25.03       25.31
392                 //  1/50    29.80       231.9       30.44
393                 //  1/20    5376        146e3       10e3
394                 //  1/10    829e3       22e6        1.5e6
395                 //  1/5     157e6       3.8e9       280e6
396 
397                 final var ssig2 = ssig1 * csig12 + csig1 * ssig12;
398                 final var csig2 = csig1 * csig12 - ssig1 * ssig12;
399                 b12 = Geodesic.sinCosSeries(true, ssig2, csig2, c1a);
400 
401                 final var serr = (1 + a1m1) * (sig12 + (b12 - b11)) - s12A12 / b;
402                 sig12 = sig12 - serr / Math.sqrt(1 + k2 * GeoMath.sq(ssig2));
403                 ssig12 = Math.sin(sig12);
404                 csig12 = Math.cos(sig12);
405                 // update b12 below
406             }
407             r.setA12(Math.toDegrees(sig12));
408         }
409 
410         final var ssig2 = ssig1 * csig12 + csig1 * ssig12;
411         var csig2 = csig1 * csig12 - ssig1 * ssig12;
412         final double sbet2;
413         double cbet2;
414         final double salp2;
415         final double calp2;
416         // sig2 = sig1 + sig12
417 
418 
419         final var dn2 = Math.sqrt(1 + k2 * GeoMath.sq(ssig2));
420         if ((outmask & (GeodesicMask.DISTANCE | GeodesicMask.REDUCED_LENGTH | GeodesicMask.GEODESIC_SCALE)) != 0) {
421             if (arcmode || Math.abs(f) > 0.01) {
422                 b12 = Geodesic.sinCosSeries(true, ssig2, csig2, c1a);
423             }
424             ab1 = (1 + a1m1) * (b12 - b11);
425         }
426 
427         // sin(bet2) = cos(alp0) * sin(sig2)
428         sbet2 = calp0 * ssig2;
429         // alt: cbet2 = hypot(csig2, salp0 * ssig2)
430         cbet2 = GeoMath.hypot(salp0, calp0 * csig2);
431         if (cbet2 == 0) {
432             // i.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case
433             cbet2 = csig2 = Geodesic.TINY;
434         }
435 
436         // tan(alp0) = cos(sig2) * tan(alp2)
437         salp2 = salp0;
438         // no need to normalize
439         calp2 = calp0 * csig2;
440 
441         if ((outmask & GeodesicMask.DISTANCE) != 0 && arcmode) {
442             r.setS12(b * ((1 + a1m1) * sig12 + ab1));
443         }
444 
445         if ((outmask & GeodesicMask.LONGITUDE) != 0) {
446             // tan(omg2) = sin(alp0) * tan(sig2)
447 
448             // no need to normalize east or west going?
449             //noinspection all
450             final var somg2 = salp0 * ssig2;
451             final var e = GeoMath.copysign(1, salp0);
452 
453             // omg12 = omg2 - omg1
454             final var omg12 = ((outmask & GeodesicMask.LONG_UNROLL) != 0)
455                     ? e * (sig12 - (Math.atan2(ssig2, csig2) - Math.atan2(ssig1, csig1))
456                     + (Math.atan2(e * somg2, csig2) - Math.atan2(e * somg1, comg1)))
457                     : Math.atan2(somg2 * comg1 - csig2 * somg1, csig2 * comg1 + somg2 * somg1);
458 
459             final var lam12 = omg12 + a3c * (sig12 + (Geodesic.sinCosSeries(true, ssig2, csig2, c3a) - b31));
460             final var lon12 = Math.toDegrees(lam12);
461             r.setLon2(((outmask & GeodesicMask.LONG_UNROLL) != 0)
462                     ? lon1 + lon12 : GeoMath.angNormalize(r.getLon1() + GeoMath.angNormalize(lon12)));
463         }
464 
465         if ((outmask & GeodesicMask.LATITUDE) != 0) {
466             r.setLat2(GeoMath.atan2d(sbet2, f1 * cbet2));
467         }
468 
469         if ((outmask & GeodesicMask.AZIMUTH) != 0) {
470             r.setAzi2(GeoMath.atan2d(salp2, calp2));
471         }
472 
473         if ((outmask & (GeodesicMask.REDUCED_LENGTH | GeodesicMask.GEODESIC_SCALE)) != 0) {
474             final var b22 = Geodesic.sinCosSeries(true, ssig2, csig2, c2a);
475             final var ab2 = (1 + a2m1) * (b22 - b21);
476             final var j12 = (a1m1 - a2m1) * sig12 + (ab1 - ab2);
477             if ((outmask & GeodesicMask.REDUCED_LENGTH) != 0) {
478                 // add parens around (mCsig1 * ssig2) and (mSsig1 * csig2) to ensure
479                 // accurate cancellation in the case of coincident points
480                 r.setM12(b * ((dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2)) - csig1 * csig2 * j12));
481             }
482             if ((outmask & GeodesicMask.GEODESIC_SCALE) != 0) {
483                 final var t = k2 * (ssig2 - ssig1) * (ssig2 + ssig1) / (dn1 + dn2);
484                 r.setScaleM12(csig12 + (t * ssig2 - csig2 * j12) * ssig1 / dn1);
485                 r.setScaleM21(csig12 - (t * ssig1 - csig1 * j12) * ssig2 / dn2);
486             }
487         }
488 
489         if ((outmask & GeodesicMask.AREA) != 0) {
490             final var b42 = Geodesic.sinCosSeries(false, ssig2, csig2, c4a);
491             final double salp12;
492             final double calp12;
493             if (calp0 == 0 || salp0 == 0) {
494                 //alp12 = alp2 - alp1, used in atan2 so no need to normalize
495                 salp12 = salp2 * calp1 - calp2 * salp1;
496                 calp12 = calp2 * calp1 + salp2 * salp1;
497             } else {
498                 // tan(alp) = tan(alp0) * sec(sig)
499                 // tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1)
500                 // = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2)
501                 // if csig12 > 0, write
502                 //   csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1)
503                 // else
504                 //   csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1
505                 // no need to normalize
506                 salp12 = calp0 * salp0 * (csig12 <= 0
507                         ? csig1 * (1 - csig12) + ssig12 * ssig1
508                         : ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1));
509                 calp12 = GeoMath.sq(salp0) + GeoMath.sq(calp0) * csig1 * csig2;
510             }
511             r.setAreaS12(c2 * Math.atan2(salp12, calp12) + a4 * (b42 - b41));
512         }
513 
514         return r;
515     }
516 
517     /**
518      * Specify position of point 3 in terms of distance.
519      * This is only useful if the GeodesicLine object has been constructed with <i>caps</i> |=
520      * {@link GeodesicMask#DISTANCE_IN}.
521      *
522      * @param s13 the distance from point 1 to point 3 (meters); it can be negative.
523      */
524     public void setDistance(final double s13) {
525         this.s13 = s13;
526         final var g = position(false, this.s13, 0);
527         a13 = g.getA12();
528     }
529 
530     /**
531      * Specify position of point 3 in terms of either distance or arc length.
532      *
533      * @param arcmode boolean flag determining the meaning of the second parameter; if <i>arcmode</i>
534      *                is false, then the GeodesicLine object must have been constructed with
535      *                <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN}.
536      * @param s13A13  if <i>arcmode</i> is false, this is the distance from point 1 to point 3 (meters);
537      *                otherwise it is the arc length from point 1 to point 3 (degrees); it can be
538      *                negative.
539      */
540     public void genSetDistance(final boolean arcmode, final double s13A13) {
541         if (arcmode) {
542             setArc(s13A13);
543         } else {
544             setDistance(s13A13);
545         }
546     }
547 
548     /**
549      * Gets the latitude of point 1 (degrees).
550      *
551      * @return <i>lat1</i> the latitude of point 1 (degrees).
552      */
553     public double getLatitude() {
554         return init() ? lat1 : Double.NaN;
555     }
556 
557     /**
558      * Gets the longitude of point 1 (degrees).
559      *
560      * @return <i>lon1</i> the longitude of point 1 (degrees).
561      */
562     public double getLongitude() {
563         return init() ? lon1 : Double.NaN;
564     }
565 
566     /**
567      * Gets the azimuth (degrees) of the geodesic line at point 1.
568      *
569      * @return <i>azi1</i> the azimuth (degrees) of the geodesic line at point 1.
570      */
571     public double getAzimuth() {
572         return init() ? azi1 : Double.NaN;
573     }
574 
575     /**
576      * Gets a pair of sine and cosine of <i>azi1</i> the azimuth (degrees) of the geodesic line at
577      * point 1.
578      *
579      * @return pair of sine and cosine of <i>azi1</i> the azimuth (degrees) of the geodesic line
580      * at point 1.
581      */
582     public Pair getAzimuthCosines() {
583         return new Pair(init() ? salp1 : Double.NaN, init() ? calp1 : Double.NaN);
584     }
585 
586     /**
587      * Gets the azimuth (degrees) of the geodesic line as it crosses the equator in a northward
588      * direction.
589      *
590      * @return <i>azi0</i> the azimuth (degrees) of the geodesic line as it crosses the equator in a
591      * northward direction.
592      */
593     public double getEquatorialAzimuth() {
594         return init() ? GeoMath.atan2d(salp0, calp0) : Double.NaN;
595     }
596 
597     /**
598      * Gets a pair of sine and cosine of <i>azi0</i> the azimuth of the godesic line as it crosses the
599      * equator in a northward direction.
600      *
601      * @return pair of sine and cosine of <i>azi0</i> the azimuth of the godesic line as it crosses
602      * the equator in a northward direction.
603      */
604     public Pair getEquatorialAzimuthCosines() {
605         return new Pair(init() ? salp0 : Double.NaN, init() ? calp0 : Double.NaN);
606     }
607 
608     /**
609      * Gets the arc length (degrees) between the northward equatorial crossing and point 1.
610      *
611      * @return <i>a1</i> the arc length (degrees) between the northward equatorial crossing and
612      * point 1.
613      */
614     public double getEquatorialArc() {
615         return init() ? GeoMath.atan2d(ssig1, csig1) : Double.NaN;
616     }
617 
618     /**
619      * Gets the equatorial radius of the ellipsoid (meters). This is the value inherited from the
620      * Geodesic object used in the constructor.
621      *
622      * @return <i>a</i> the equatorial radius of the ellipsoid (meters).
623      */
624     public double getMajorRadius() {
625         return init() ? a : Double.NaN;
626     }
627 
628     /**
629      * Gets the flattening of the ellipsoid. This is the value inherited from the Geodesic object
630      * used in the constructor.
631      *
632      * @return <i>f</i> the flattening of the ellipsoid.
633      */
634     public double getFlattening() {
635         return init() ? f : Double.NaN;
636     }
637 
638     /**
639      * Gets the computational capabilities that this object was constructed with. LATITUDE and AZIMUTH
640      * are always included.
641      *
642      * @return <i>caps</i> the computation capabilities that this object was constructed with.
643      */
644     public int getCapabilities() {
645         return caps;
646     }
647 
648     /**
649      * Indicates whether this GeodesicLine object has all tested capabilities
650      *
651      * @param testcaps a set of bitor'ed {@link GeodesicMask} values.
652      * @return true if the GeodesicLine object has all these capabilities.
653      */
654     public boolean capabilities(int testcaps) {
655         testcaps &= GeodesicMask.OUT_ALL;
656         return (caps & testcaps) == testcaps;
657     }
658 
659     /**
660      * The distance or arc length to point 3.
661      *
662      * @param arcmode boolean flag determining the meaning of returned value.
663      * @return <i>s13</i> if <i>arcmode</i> is false; <i>a13</i> if <i>arcmode</i> is true.
664      */
665     public double genDistance(final boolean arcmode) {
666         final var tmp = arcmode ? a13 : s13;
667         return init() ? tmp : Double.NaN;
668     }
669 
670     /**
671      * Gets the distance to point 3 (meters).
672      *
673      * @return <i>s13</i> the disance to point 3 (meters).
674      */
675     public double getDistance() {
676         return genDistance(false);
677     }
678 
679     /**
680      * Gets the arc length to point 3 (degrees).
681      *
682      * @return <i>a13</i> the arc length to point 3 (degrees).
683      */
684     public double getArc() {
685         return genDistance(true);
686     }
687 
688     /**
689      * Specify position of point 3 in terms of arc length.
690      * The distance <i>s13</i> is only set if the GeodesicLine object has been constructed with
691      * <i>caps</i> |= {@link GeodesicMask#DISTANCE}.
692      *
693      * @param a13 the arc length from point 1 to point 3 (degrees); it can be negative.
694      */
695     void setArc(final double a13) {
696         this.a13 = a13;
697         final var g = position(true, this.a13, GeodesicMask.DISTANCE);
698         s13 = g.getS12();
699     }
700 
701     /**
702      * @return true if the object has been initialized.
703      */
704     private boolean init() {
705         return caps != 0;
706     }
707 
708     private void lineInit(
709             final Geodesic g, final double lat1, final double lon1, final double azi1, final double salp1,
710             final double calp1, final int caps) {
711         a = g.a;
712         f = g.f;
713         b = g.b;
714         c2 = g.c2;
715         f1 = g.f1;
716 
717         // always allow latitude and azimuth and unrolling the longitude
718         this.caps = caps | GeodesicMask.LATITUDE | GeodesicMask.AZIMUTH | GeodesicMask.LONG_UNROLL;
719 
720         this.lat1 = GeoMath.latFix(lat1);
721         this.lon1 = lon1;
722         this.azi1 = azi1;
723         this.salp1 = salp1;
724         this.calp1 = calp1;
725 
726         var p = GeoMath.sincosd(GeoMath.angRound(this.lat1));
727         var sbet1 = f1 * p.getFirst();
728         var cbet1 = p.getSecond();
729 
730         // ensure cbet1 = +epsilon at poles
731         p = GeoMath.norm(sbet1, cbet1);
732         sbet1 = p.getFirst();
733         cbet1 = Math.max(Geodesic.TINY, p.getSecond());
734 
735         dn1 = Math.sqrt(1 + g.ep2 * GeoMath.sq(sbet1));
736 
737         // evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0),
738 
739         // alp0 in [0, pi/2 - |bet1|]
740         salp0 = this.salp1 * cbet1;
741 
742         // alt: calp0 = hypot(sbet1, calp1 * cbet1). The following is slightly
743         // better (consider the case salp1 = 0).
744         calp0 = GeoMath.hypot(this.calp1, this.salp1 * sbet1);
745 
746         // Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1).
747         // sig = 0 is nearest northward crossing of the equator.
748         // With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line).
749         // With bet1 = pi/2, alp1 = -pi, sig1 = pi/2
750         // With bet1 = -pi/2, alp1 = 0, sig1 = -pi/2
751         // Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1).
752         // With alp0 in (0, pi/2], quadrants for sig and omg coincide.
753         // No atan2(0,0) ambiguity at poles since cbet1 = +epsilon
754         // With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi.
755         ssig1 = sbet1;
756         somg1 = salp0 * sbet1;
757         csig1 = comg1 = sbet1 != 0 || this.calp1 != 0 ? cbet1 * this.calp1 : 1;
758 
759         p = GeoMath.norm(ssig1, csig1);
760         ssig1 = p.getFirst();
761         // sig 1 in (-pi, pi]
762         csig1 = p.getSecond();
763 
764         // GeoMath.norm(mSomg1, mComg1); -- don't need to normalize!
765 
766         k2 = GeoMath.sq(calp0) * g.ep2;
767         final var eps = k2 / (2 * (1 + Math.sqrt(1 + k2)) + k2);
768 
769         if ((this.caps & GeodesicMask.CAP_C1) != 0) {
770             a1m1 = Geodesic.a1m1f(eps);
771             c1a = new double[NC1 + 1];
772             Geodesic.c1f(eps, c1a);
773             b11 = Geodesic.sinCosSeries(true, ssig1, csig1, c1a);
774             final var s = Math.sin(b11);
775             final var c = Math.cos(b11);
776             // tau1 = sig1 + b11
777             stau1 = ssig1 * c + csig1 * s;
778             ctau1 = csig1 * c - ssig1 * s;
779             // not necessary because c1pa rverts c1a
780             // mB11 = -sinCosSeries(true, mStau1, mCtau1, mC1pa, NC1P)
781         }
782 
783         if ((this.caps & GeodesicMask.CAP_C1P) != 0) {
784             c1pa = new double[NC1P + 1];
785             Geodesic.c1pf(eps, c1pa);
786         }
787 
788         if ((this.caps & GeodesicMask.CAP_C2) != 0) {
789             c2a = new double[NC2 + 1];
790             a2m1 = Geodesic.a2m1f(eps);
791             Geodesic.c2f(eps, c2a);
792             b21 = Geodesic.sinCosSeries(true, ssig1, csig1, c2a);
793         }
794 
795         if ((this.caps & GeodesicMask.CAP_C3) != 0) {
796             c3a = new double[NC3];
797             g.c3f(eps, c3a);
798             a3c = -f * salp0 * g.a3f(eps);
799             b31 = Geodesic.sinCosSeries(true, ssig1, csig1, c3a);
800         }
801 
802         if ((this.caps & GeodesicMask.CAP_C4) != 0) {
803             c4a = new double[NC4];
804             g.c4f(eps, c4a);
805             // multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0)
806             a4 = GeoMath.sq(a) * calp0 * salp0 * g.e2;
807             b41 = Geodesic.sinCosSeries(false, ssig1, csig1, c4a);
808         }
809     }
810 }