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 }