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° 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°.
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°. 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> − (1 − <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> − (1 − <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> = −<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>] → [<i>azi2</i>, <i>azi1</i>], [<i>M12</i>, <i>M21</i>]
115 * → [<i>M21</i>, <i>M12</i>], <i>S12</i> → −<i>S12</i>.
116 * (This occurs when the longitude difference is near ±180° for oblate ellipsoids.)
117 * </li>
118 * <li>
119 * <i>lon2</i> = <i>lon1</i> + 180° (with neither point at pole). If <i>azi1</i> = 0° or
120 * ±180°, the geodesic is unique. Otherwise there are two geodesics and the second one is obtained
121 * by setting [<i>azi1</i>, <i>azi2</i>] → [−<i>azi1</i>, −<i>azi2</i>], <i>S12</i> →
122 * − <i>S12</i>. (This occurs when <i>lat2</i> is near −<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>] → [<i>azi1</i>, <i>azi2</i>] + [<i>d</i>, −<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>] → [<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 * < 0.02; however reasonably accurate results will be obtained for |<i>f</i>| < 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–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() < 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 − <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> = ±(90° − ε), and taking the limit ε → 0+. An arc length greater
316 * than 180° 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°.)
318 *
319 * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range [−90°, 90°].
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 [−180°, 180°].
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 [−180°, 180°], 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> = ±(90° − ε), and taking the limit ε → 0+. An arc length
354 * greater than 180° 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°.)
356 *
357 * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range [−90°, 90°].
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 [−180°, 180°].
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 [−180°, 180°], 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 * − <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 [−180°, 180°].
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 [−90°, 90°].
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 [−90°, 90°].
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 [−90°, 90°].
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 [−90°, 90°].
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 [−90°, 90°].
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 [−90°, 90°]. The values of
555 * <i>azi1</i> and <i>azi2</i> returned are in the range [−180°, 180°].
556 * If either point is at a pole, the azimuth is defined by keeping the longitude fixed, writing
557 * <i>lat</i> = ±(90° − ε), taking the limit ε → 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> − <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 [−180°, 180°].
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 [−90°, 90°].
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 [−90°, 90°].
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> = ± (90 − ε), taking the limit ε → 0+.
679 *
680 * @param lat1 latitude of point 1 (degrees). <i>lat1</i> should be in the range
681 * [−90°, 90°].
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> = ± (90 − ε), and taking the limit ε → 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 }