├── .gitignore ├── HOWTO-RELEASE.txt ├── Makefile ├── README.md ├── direct ├── pom.xml └── src │ └── main │ └── java │ └── Direct.java ├── inverse ├── pom.xml └── src │ └── main │ └── java │ └── Inverse.java ├── planimeter ├── pom.xml └── src │ └── main │ └── java │ └── Planimeter.java ├── pom.xml └── src ├── main └── java │ └── net │ └── sf │ └── geographiclib │ ├── Accumulator.java │ ├── Constants.java │ ├── GeoMath.java │ ├── Geodesic.java │ ├── GeodesicData.java │ ├── GeodesicLine.java │ ├── GeodesicMask.java │ ├── GeographicErr.java │ ├── Gnomonic.java │ ├── GnomonicData.java │ ├── Pair.java │ ├── PolygonArea.java │ ├── PolygonResult.java │ └── package-info.java └── test └── java └── net └── sf └── geographiclib ├── GeodesicTest.java └── SignTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.class 3 | target/ 4 | distrib-Java/ 5 | -------------------------------------------------------------------------------- /HOWTO-RELEASE.txt: -------------------------------------------------------------------------------- 1 | Maven life cycle 2 | https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html 3 | clean validate compile test package verify install deploy 4 | with -P release extra stuff is done with 5 | package: javadoc sources 6 | verify: sign 7 | 8 | make targets 9 | all 10 | test 11 | clean 12 | sanitize 13 | stage-{doc,dist} 14 | deploy-{doc,dist} - deploy to sourceforge 15 | deploy - deploy to Maven Central 16 | 17 | Version update checks 18 | java/pom.xml java/*/pom.xml 19 | java/src/main/java/net/sf/geographiclib/package-info.java (date + 20 | update change log) 21 | use make checkversion 22 | (remember to remove SNAPSHOT from version number of lib in test programs) 23 | remove SNAPSHOT versions in distrib-Java 24 | 25 | Artifact shows up on 26 | https://search.maven.org/artifact/net.sf.geographiclib/GeographicLib-Java 27 | about a day later. 28 | 29 | Maven versions 30 | ‎ 1.52 2021-06-22 31 | ‎ 1.51 2020-11-22 32 | ‎ 1.50 2019-09-24 33 | ‎ 1.49 2017-10-05 34 | ‎ 1.48 2017-04-09 35 | ‎ 1.47 2017-02-15 36 | ‎ 1.46 2016-02-14 37 | ‎ 1.45 2015-09-30 38 | ‎ 1.44 2015-08-14 39 | ‎ 1.43 2015-05-22 40 | ‎ 1.42 2015-04-27 41 | 42 | First Java implementation 43 | 1.31 2013-07-01 44 | 45 | TODO: Add documentation about areas after description of multiple 46 | shortest geodesics. 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | USER = karney 2 | STAGE = $(HOME)/web 3 | WEBSTAGE = $(STAGE)/geographiclib-web 4 | FRSSTAGE = $(STAGE)/geographiclib-files 5 | WEBDEPLOY = $(USER),geographiclib@web.sourceforge.net:./htdocs 6 | FRSDEPLOY = $(USER)@frs.sourceforge.net:/home/frs/project/geographiclib 7 | FULLVERSION = $(shell grep /version pom.xml | tr '<>\n' / | cut -d/ -f3) 8 | VERSION = $(shell echo $(FULLVERSION) | sed s/-.*//) 9 | SOURCEDIR = src/main/java/net/sf/geographiclib 10 | TESTDIR = src/test/java/net/sf/geographiclib 11 | 12 | SOURCES= \ 13 | $(SOURCEDIR)/Accumulator.java \ 14 | $(SOURCEDIR)/Constants.java \ 15 | $(SOURCEDIR)/GeoMath.java \ 16 | $(SOURCEDIR)/Geodesic.java \ 17 | $(SOURCEDIR)/GeodesicData.java \ 18 | $(SOURCEDIR)/GeodesicLine.java \ 19 | $(SOURCEDIR)/GeodesicMask.java \ 20 | $(SOURCEDIR)/GeographicErr.java \ 21 | $(SOURCEDIR)/Gnomonic.java \ 22 | $(SOURCEDIR)/GnomonicData.java \ 23 | $(SOURCEDIR)/Pair.java \ 24 | $(SOURCEDIR)/PolygonArea.java \ 25 | $(SOURCEDIR)/PolygonResult.java \ 26 | $(SOURCEDIR)/package-info.java 27 | 28 | TESTS = $(TESTDIR)/GeodesicTest.java 29 | 30 | PACKAGES= \ 31 | target/GeographicLib-Java-$(FULLVERSION).jar \ 32 | target/GeographicLib-Java-$(FULLVERSION)-javadoc.jar \ 33 | target/GeographicLib-Java-$(FULLVERSION)-sources.jar 34 | 35 | all: $(PACKAGES) 36 | 37 | $(PACKAGES) : $(SOURCES) $(TESTS) 38 | mvn -q package -P release 39 | 40 | install: $(PACKAGES) 41 | mvn -q install -P release 42 | 43 | test: 44 | mvn -q test 45 | 46 | stage-doc: $(PACKAGES) 47 | rsync -a --delete target/apidocs/ $(WEBSTAGE)/htdocs/Java/$(VERSION)/ 48 | 49 | deploy-doc: 50 | rsync --delete -av -e ssh $(WEBSTAGE)/htdocs/Java $(WEBDEPLOY)/ 51 | 52 | stage-dist: $(PACKAGES) 53 | cp -p $^ distrib-Java 54 | rsync --delete -av --exclude '*~' --delete-excluded distrib-Java $(FRSSTAGE)/ 55 | 56 | deploy-dist: 57 | rsync --delete -av $(FRSSTAGE)/distrib-Java $(FRSDEPLOY) 58 | 59 | deploy: 60 | mvn clean deploy -P release 61 | 62 | sanitize: checktrailingspace checktabs checkblanklines 63 | 64 | checktrailingspace: 65 | @echo "Looking for trailing spaces" 66 | @git ls-files | xargs grep '[ ]$$' || true 67 | 68 | checktabs: 69 | @echo "Looking for tabs" 70 | @git ls-files | grep -v Makefile | \ 71 | xargs grep ' ' || true 72 | 73 | checkblanklines: 74 | @echo "Looking for extra blank lines" 75 | @git ls-files | \ 76 | while read f; do tr 'X\n' 'YX' < $$f | \ 77 | egrep '(^X|XXX|XX$$|[^X]$$)' > /dev/null && echo $$f; done || true 78 | 79 | clean: 80 | mvn clean 81 | 82 | reallyclean: clean 83 | rm -rf $(HOME)/.m2/repository/net/sf/geographiclib/GeographicLib-Java/$(FULLVERSION) 84 | 85 | checkversion: 86 | grep "$(FULLVERSION)" pom.xml \ 87 | direct/pom.xml inverse/pom.xml planimeter/pom.xml 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java implementation of the geodesic routines in GeographicLib 2 | 3 | This is a library to solve geodesic problems on an ellipsoid model of 4 | the earth. 5 | 6 | Licensed under the MIT/X11 License; see 7 | [LICENSE.txt](https://geographiclib.sourceforge.io/LICENSE.txt). 8 | 9 | The algorithms are documented in 10 | 11 | * C. F. F. Karney, 12 | [Algorithms for geodesics](https://doi.org/10.1007/s00190-012-0578-z), 13 | J. Geodesy **87**(1), 43–55 (2013); 14 | [Addenda](https://geographiclib.sourceforge.io/miscgeod-addenda.html). 15 | 16 | The Java package is available 17 | [here](https://search.maven.org/artifact/net.sf.geographiclib/GeographicLib-Java). 18 | 19 | Here is the documentation on the 20 | [application programming interface](https://geographiclib.sourceforge.io/Java/doc/) 21 | 22 | You can build the example programs `direct`, `inverse`, or 23 | `planimeter`, with, for example: 24 | ```sh 25 | cd inverse 26 | mvn compile 27 | echo -30 0 29.5 179.5 | mvn -q exec:java 28 | ``` 29 | The ouput should be 30 | ``` 31 | 154.37818274278 25.48587026077 19937782.280350 32 | ``` 33 | -------------------------------------------------------------------------------- /direct/pom.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 4.0.0 8 | 9 | net.sf.geographiclib.example 10 | Direct 11 | Direct 12 | 2.0.1-SNAPSHOT 13 | 14 | jar 15 | 16 | 17 | . 18 | 1.8 19 | 2.9 20 | UTF-8 21 | 2.3.2 22 | 2.4 23 | 2.8 24 | 3.0 25 | 26 | 27 | 28 | 29 | net.sf.geographiclib 30 | GeographicLib-Java 31 | 2.0.1-SNAPSHOT 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-compiler-plugin 40 | ${maven-compiler-plugin.version} 41 | 42 | ${java.version} 43 | ${java.version} 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-surefire-plugin 50 | ${surefire-plugin.version} 51 | 52 | false 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-javadoc-plugin 59 | ${maven-javadoc.version} 60 | 61 | public 62 | true 63 | 64 | 65 | 66 | attach-javadocs 67 | 68 | jar 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.codehaus.mojo 76 | exec-maven-plugin 77 | 1.2.1 78 | 79 | Direct 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /direct/src/main/java/Direct.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A test program for the GeographicLib.Geodesic.Direct method 3 | **********************************************************************/ 4 | 5 | import java.util.*; 6 | import net.sf.geographiclib.*; 7 | public class Direct { 8 | /** 9 | * Solve the direct geodesic problem. 10 | * 11 | * This program reads in lines with lat1, lon1, azi1, s12 and prints 12 | * out lines with lat2, lon2, azi2 (for the WGS84 ellipsoid). 13 | **********************************************************************/ 14 | public static void main(String[] args) { 15 | try { 16 | Scanner in = new Scanner(System.in); 17 | double lat1, lon1, azi1, s12; 18 | while (true) { 19 | lat1 = in.nextDouble(); lon1 = in.nextDouble(); 20 | azi1 = in.nextDouble(); s12 = in.nextDouble(); 21 | GeodesicData g = Geodesic.WGS84.Direct(lat1, lon1, azi1, s12); 22 | System.out.format("%.11f %.11f %.11f%n", g.lat2, g.lon2, g.azi2); 23 | } 24 | } 25 | catch (Exception e) {} 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /inverse/pom.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 4.0.0 8 | 9 | net.sf.geographiclib.example 10 | Inverse 11 | Inverse 12 | 2.0.1-SNAPSHOT 13 | 14 | jar 15 | 16 | 17 | . 18 | 1.8 19 | 2.9 20 | UTF-8 21 | 2.3.2 22 | 2.4 23 | 2.8 24 | 3.0 25 | 26 | 27 | 28 | 29 | net.sf.geographiclib 30 | GeographicLib-Java 31 | 2.0.1-SNAPSHOT 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-compiler-plugin 40 | ${maven-compiler-plugin.version} 41 | 42 | ${java.version} 43 | ${java.version} 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-surefire-plugin 50 | ${surefire-plugin.version} 51 | 52 | false 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-javadoc-plugin 59 | ${maven-javadoc.version} 60 | 61 | public 62 | true 63 | 64 | 65 | 66 | attach-javadocs 67 | 68 | jar 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.codehaus.mojo 76 | exec-maven-plugin 77 | 1.2.1 78 | 79 | Inverse 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /inverse/src/main/java/Inverse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A test program for the GeographicLib.Geodesic.Inverse method 3 | **********************************************************************/ 4 | 5 | import java.util.*; 6 | import net.sf.geographiclib.*; 7 | /** 8 | * Solve the inverse geodesic problem. 9 | * 10 | * This program reads in lines with lat1, lon1, lat2, lon2 and prints 11 | * out lines with azi1, azi2, s12 (for the WGS84 ellipsoid). 12 | **********************************************************************/ 13 | public class Inverse { 14 | public static void main(String[] args) { 15 | try { 16 | Scanner in = new Scanner(System.in); 17 | double lat1, lon1, lat2, lon2; 18 | while (true) { 19 | lat1 = in.nextDouble(); lon1 = in.nextDouble(); 20 | lat2 = in.nextDouble(); lon2 = in.nextDouble(); 21 | GeodesicData g = Geodesic.WGS84.Inverse(lat1, lon1, lat2, lon2); 22 | System.out.format("%.11f %.11f %.6f%n", g.azi1, g.azi2, g.s12); 23 | } 24 | } 25 | catch (Exception e) {} 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /planimeter/pom.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 4.0.0 8 | 9 | net.sf.geographiclib.example 10 | Planimeter 11 | Planimeter 12 | 2.0.1-SNAPSHOT 13 | 14 | jar 15 | 16 | 17 | . 18 | 1.8 19 | 2.9 20 | UTF-8 21 | 2.3.2 22 | 2.4 23 | 2.8 24 | 3.0 25 | 26 | 27 | 28 | 29 | net.sf.geographiclib 30 | GeographicLib-Java 31 | 2.0.1-SNAPSHOT 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-compiler-plugin 40 | ${maven-compiler-plugin.version} 41 | 42 | ${java.version} 43 | ${java.version} 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-surefire-plugin 50 | ${surefire-plugin.version} 51 | 52 | false 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-javadoc-plugin 59 | ${maven-javadoc.version} 60 | 61 | public 62 | true 63 | 64 | 65 | 66 | attach-javadocs 67 | 68 | jar 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.codehaus.mojo 76 | exec-maven-plugin 77 | 1.2.1 78 | 79 | Planimeter 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /planimeter/src/main/java/Planimeter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A test program for the GeographicLib.PolygonArea class 3 | **********************************************************************/ 4 | 5 | import java.util.*; 6 | import net.sf.geographiclib.*; 7 | /** 8 | * Compute the area of a geodesic polygon. 9 | * 10 | * This program reads lines with lat, lon for each vertex of a polygon. 11 | * At the end of input, the program prints the number of vertices, the 12 | * perimeter of the polygon and its area (for the WGS84 ellipsoid). 13 | **********************************************************************/ 14 | public class Planimeter { 15 | public static void main(String[] args) { 16 | PolygonArea p = new PolygonArea(Geodesic.WGS84, false); 17 | try { 18 | Scanner in = new Scanner(System.in); 19 | while (true) { 20 | double lat = in.nextDouble(), lon = in.nextDouble(); 21 | p.AddPoint(lat, lon); 22 | } 23 | } 24 | catch (Exception e) {} 25 | PolygonResult r = p.Compute(); 26 | System.out.format("%d %.6f %.2f%n", r.num, r.perimeter, r.area); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 4.0.0 8 | 9 | net.sf.geographiclib 10 | GeographicLib-Java 11 | 2.0.1-SNAPSHOT 12 | 13 | jar 14 | 15 | 16 | 17 | The MIT License(MIT) 18 | http://opensource.org/licenses/MIT 19 | 20 | 21 | 22 | Java implementation of GeographicLib 23 | 24 | This is a Java implementation of the geodesic algorithms from 25 | GeographicLib. This is a self-contained library to solve geodesic 26 | problems on an ellipsoid model of the earth. It requires Java 27 | version 1.8 or later. 28 | 29 | https://geographiclib.sourceforge.io 30 | 31 | 32 | 33 | Charles Karney 34 | charles@karney.com 35 | 36 | 37 | https://www.petrel.org 38 | 39 | 40 | 41 | 42 | 43 | . 44 | 1.8 45 | 2.9 46 | UTF-8 47 | 2.3.2 48 | 2.4 49 | 2.8 50 | 3.0 51 | 52 | 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-compiler-plugin 58 | ${maven-compiler-plugin.version} 59 | 60 | ${java.version} 61 | ${java.version} 62 | 63 | 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-surefire-plugin 68 | ${surefire-plugin.version} 69 | 70 | false 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | release 80 | 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-javadoc-plugin 86 | ${maven-javadoc.version} 87 | 88 | 8 89 | public 90 | true 91 | 92 | 93 | 94 | attach-javadocs 95 | 96 | jar 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-source-plugin 105 | 2.2.1 106 | 107 | 108 | attach-sources 109 | 110 | jar-no-fork 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.sonatype.plugins 118 | nexus-staging-maven-plugin 119 | 1.6.3 120 | true 121 | 122 | ossrh 123 | https://oss.sonatype.org/ 124 | true 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-gpg-plugin 131 | 1.5 132 | 133 | 134 | sign-artifacts 135 | verify 136 | 137 | sign 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | ossrh 151 | https://oss.sonatype.org/content/repositories/snapshots 152 | 153 | 154 | ossrh 155 | https://oss.sonatype.org/service/local/staging/deploy/maven2 156 | 157 | 158 | 159 | 160 | 161 | scm:git:git@github.com:geographiclib/geographiclib-java 162 | 163 | 164 | scm:git:https://github.com/geographiclib/geographiclib-java 165 | 166 | 167 | https://github.com/geographiclib/geographiclib-java 168 | 169 | 170 | 171 | 172 | 173 | junit 174 | junit 175 | 4.12 176 | test 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/Accumulator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.Accumulator class 3 | * 4 | * Copyright (c) Charles Karney (2013-2020) and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * An accumulator for sums. 12 | *

13 | * This allow many double precision numbers to be added together with twice the 14 | * normal precision. Thus the effective precision of the sum is 106 bits or 15 | * about 32 decimal places. 16 | *

17 | * The implementation follows J. R. Shewchuk, 18 | * Adaptive Precision 19 | * Floating-Point Arithmetic and Fast Robust Geometric Predicates, 20 | * Discrete & Computational Geometry 18(3) 305–363 (1997). 21 | *

22 | * In the documentation of the member functions, sum stands for the 23 | * value currently held in the accumulator. 24 | ***********************************************************************/ 25 | public class Accumulator { 26 | // _s + _t accumulators for the sum. 27 | private double _s, _t; 28 | /** 29 | * Construct from a double. 30 | *

31 | * @param y set sum = y. 32 | **********************************************************************/ 33 | public Accumulator(double y) { _s = y; _t = 0; } 34 | /** 35 | * Construct from another Accumulator. 36 | *

37 | * @param a set sum = a. 38 | **********************************************************************/ 39 | public Accumulator(Accumulator a) { _s = a._s; _t = a._t; } 40 | /** 41 | * Set the value to a double. 42 | *

43 | * @param y set sum = y. 44 | **********************************************************************/ 45 | public void Set(double y) { _s = y; _t = 0; } 46 | /** 47 | * Return the value held in the accumulator. 48 | *

49 | * @return sum. 50 | **********************************************************************/ 51 | public double Sum() { return _s; } 52 | /** 53 | * Return the result of adding a number to sum (but don't change 54 | * sum). 55 | *

56 | * @param y the number to be added to the sum. 57 | * @return sum + y. 58 | **********************************************************************/ 59 | public double Sum(double y) { 60 | Pair p = new Pair(); 61 | AddInternal(p, _s, _t, y); 62 | return p.first; 63 | } 64 | /** 65 | * Internal version of Add, p = [s, t] + y 66 | *

67 | * @param s the larger part of the accumulator. 68 | * @param t the smaller part of the accumulator. 69 | * @param y the addend. 70 | * @param p output Pair(s, t) with the result. 71 | **********************************************************************/ 72 | public static void AddInternal(Pair p, double s, double t, double y) { 73 | // Here's Shewchuk's solution... 74 | double u; // hold exact sum as [s, t, u] 75 | // Accumulate starting at least significant end 76 | GeoMath.sum(p, y, t); y = p.first; u = p.second; 77 | GeoMath.sum(p, y, s); s = p.first; t = p.second; 78 | // Start is s, t decreasing and non-adjacent. Sum is now (s + t + u) 79 | // exactly with s, t, u non-adjacent and in decreasing order (except for 80 | // possible zeros). The following code tries to normalize the result. 81 | // Ideally, we want s = round(s+t+u) and u = round(s+t+u - s). The 82 | // following does an approximate job (and maintains the decreasing 83 | // non-adjacent property). Here are two "failures" using 3-bit floats: 84 | // 85 | // Case 1: s is not equal to round(s+t+u) -- off by 1 ulp 86 | // [12, -1] - 8 -> [4, 0, -1] -> [4, -1] = 3 should be [3, 0] = 3 87 | // 88 | // Case 2: s+t is not as close to s+t+u as it shold be 89 | // [64, 5] + 4 -> [64, 8, 1] -> [64, 8] = 72 (off by 1) 90 | // should be [80, -7] = 73 (exact) 91 | // 92 | // "Fixing" these problems is probably not worth the expense. The 93 | // representation inevitably leads to small errors in the accumulated 94 | // values. The additional errors illustrated here amount to 1 ulp of the 95 | // less significant word during each addition to the Accumulator and an 96 | // additional possible error of 1 ulp in the reported sum. 97 | // 98 | // Incidentally, the "ideal" representation described above is not 99 | // canonical, because s = round(s + t) may not be true. For example, 100 | // with 3-bit floats: 101 | // 102 | // [128, 16] + 1 -> [160, -16] -- 160 = round(145). 103 | // But [160, 0] - 16 -> [128, 16] -- 128 = round(144). 104 | // 105 | if (s == 0) // This implies t == 0, 106 | s = u; // so result is u 107 | else 108 | t += u; // otherwise just accumulate u to t. 109 | p.first = s; p.second = t; 110 | } 111 | 112 | /** 113 | * Add a number to the accumulator. 114 | *

115 | * @param y set sum += y. 116 | **********************************************************************/ 117 | public void Add(double y) { 118 | Pair p = new Pair(); 119 | AddInternal(p, _s, _t, y); 120 | _s = p.first; _t = p.second; 121 | } 122 | /** 123 | * Negate an accumulator. 124 | *

125 | * Set sum = −sum. 126 | **********************************************************************/ 127 | public void Negate() { _s = -_s; _t = -_t; } 128 | /** 129 | * Take the remainder. 130 | *

131 | * @param y the modulus 132 | *

133 | * Put sum in the rangle [−y, y]. 134 | **********************************************************************/ 135 | public void Remainder(double y) { 136 | _s = Math.IEEEremainder(_s, y); 137 | Add(0.0); // renormalize 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/Constants.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.Constants class 3 | * 4 | * Copyright (c) Charles Karney (2013) and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * Constants needed by GeographicLib. 12 | *

13 | * Define constants specifying the WGS84 ellipsoid. 14 | ***********************************************************************/ 15 | public class Constants { 16 | /** 17 | * The equatorial radius of WGS84 ellipsoid (6378137 m). 18 | **********************************************************************/ 19 | public static final double WGS84_a = 6378137; 20 | /** 21 | * The flattening of WGS84 ellipsoid (1/298.257223563). 22 | **********************************************************************/ 23 | public static final double WGS84_f = 1/298.257223563; 24 | 25 | private Constants() {} 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/GeoMath.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.GeoMath class 3 | * 4 | * Copyright (c) Charles Karney (2013-2020) and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * Mathematical functions needed by GeographicLib. 12 | *

13 | * Define mathematical functions and constants so that any version of Java 14 | * can be used. 15 | **********************************************************************/ 16 | public class GeoMath { 17 | /** 18 | * The number of binary digits in the fraction of a double precision 19 | * number (equivalent to C++'s {@code numeric_limits::digits}). 20 | **********************************************************************/ 21 | public static final int digits = 53; 22 | 23 | /** 24 | * Square a number. 25 | *

26 | * @param x the argument. 27 | * @return x2. 28 | **********************************************************************/ 29 | public static double sq(double x) { return x * x; } 30 | 31 | /** 32 | * The inverse hyperbolic tangent function. This is defined in terms of 33 | * Math.log1p(x) in order to maintain accuracy near x = 0. 34 | * In addition, the odd parity of the function is enforced. 35 | *

36 | * @param x the argument. 37 | * @return atanh(x). 38 | **********************************************************************/ 39 | public static double atanh(double x) { 40 | double y = Math.abs(x); // Enforce odd parity 41 | y = Math.log1p(2 * y/(1 - y))/2; 42 | return x > 0 ? y : (x < 0 ? -y : x); 43 | } 44 | 45 | /** 46 | * Normalize a sine cosine pair. 47 | *

48 | * @param p return parameter for normalized quantities with sinx2 49 | * + cosx2 = 1. 50 | * @param sinx the sine. 51 | * @param cosx the cosine. 52 | **********************************************************************/ 53 | public static void norm(Pair p, double sinx, double cosx) { 54 | double r = Math.hypot(sinx, cosx); 55 | p.first = sinx/r; p.second = cosx/r; 56 | } 57 | 58 | /** 59 | * The error-free sum of two numbers. 60 | *

61 | * @param u the first number in the sum. 62 | * @param v the second number in the sum. 63 | * @param p output Pair(s, t) with s = round(u + 64 | * v) and t = u + v - s. 65 | *

66 | * See D. E. Knuth, TAOCP, Vol 2, 4.2.2, Theorem B. 67 | **********************************************************************/ 68 | public static void sum(Pair p, double u, double v) { 69 | double s = u + v; 70 | double up = s - v; 71 | double vpp = s - up; 72 | up -= u; 73 | vpp -= v; 74 | double t = s != 0 ? 0.0 - (up + vpp) : s; 75 | // u + v = s + t 76 | // = round(u + v) + t 77 | p.first = s; p.second = t; 78 | } 79 | 80 | /** 81 | * Evaluate a polynomial. 82 | *

83 | * @param N the order of the polynomial. 84 | * @param p the coefficient array (of size N + s + 1 or more). 85 | * @param s starting index for the array. 86 | * @param x the variable. 87 | * @return the value of the polynomial. 88 | *

89 | * Evaluate y = ∑n=0..N 90 | * ps+n 91 | * xNn. Return 0 if N < 0. 92 | * Return ps, if N = 0 (even if x is 93 | * infinite or a nan). The evaluation uses Horner's method. 94 | **********************************************************************/ 95 | public static double polyval(int N, double p[], int s, double x) { 96 | double y = N < 0 ? 0 : p[s++]; 97 | while (--N >= 0) y = y * x + p[s++]; 98 | return y; 99 | } 100 | 101 | /** 102 | * Coarsen a value close to zero. 103 | *

104 | * @param x the argument 105 | * @return the coarsened value. 106 | *

107 | * This makes the smallest gap in x = 1/16 − nextafter(1/16, 0) 108 | * = 1/257 for reals = 0.7 pm on the earth if x is an angle 109 | * in degrees. (This is about 1000 times more resolution than we get with 110 | * angles around 90 degrees.) We use this to avoid having to deal with near 111 | * singular cases when x is non-zero but tiny (e.g., 112 | * 10−200). This converts −0 to +0; however tiny 113 | * negative numbers get converted to −0. 114 | **********************************************************************/ 115 | public static double AngRound(double x) { 116 | final double z = 1/16.0; 117 | double y = Math.abs(x); 118 | // The compiler mustn't "simplify" z - (z - y) to y 119 | y = y < z ? z - (z - y) : y; 120 | return Math.copySign(y, x); 121 | } 122 | 123 | /** 124 | * Normalize an angle. 125 | *

126 | * @param x the angle in degrees. 127 | * @return the angle reduced to the range [−180°, 180°). 128 | *

129 | * The range of x is unrestricted. 130 | **********************************************************************/ 131 | public static double AngNormalize(double x) { 132 | double y = Math.IEEEremainder(x, 360.0); 133 | return Math.abs(y) == 180 ? Math.copySign(180.0, x) : y; 134 | } 135 | 136 | /** 137 | * Normalize a latitude. 138 | *

139 | * @param x the angle in degrees. 140 | * @return x if it is in the range [−90°, 90°], otherwise 141 | * return NaN. 142 | **********************************************************************/ 143 | public static double LatFix(double x) { 144 | return Math.abs(x) > 90 ? Double.NaN : x; 145 | } 146 | 147 | /** 148 | * The exact difference of two angles reduced to [−180°, 180°]. 149 | *

150 | * @param x the first angle in degrees. 151 | * @param y the second angle in degrees. 152 | * @param p output Pair(d, e) with d being the rounded 153 | * difference and e being the error. 154 | *

155 | * This computes z = yx exactly, reduced to 156 | * [−180°, 180°]; and then sets z = d + e 157 | * where d is the nearest representable number to z and 158 | * e is the truncation error. If z = ±0° or 159 | * ±180°, then the sign of d is given by the sign of 160 | * yx. The maximum absolute value of e is 161 | * 2−26 (for doubles). 162 | **********************************************************************/ 163 | public static void AngDiff(Pair p, double x, double y) { 164 | sum(p, Math.IEEEremainder(-x, 360.0), Math.IEEEremainder(y, 360.0)); 165 | sum(p, Math.IEEEremainder(p.first, 360.0), p.second); 166 | if (p.first == 0 || Math.abs(p.first) == 180) 167 | // p = [d, e]... 168 | // If e == 0, take sign from y - x 169 | // else (e != 0, implies d = +/-180), d and e must have opposite signs 170 | p.first = Math.copySign(p.first, p.second == 0 ? y - x : -p.second); 171 | } 172 | 173 | /** 174 | * Evaluate the sine and cosine function with the argument in degrees 175 | * 176 | * @param p return Pair(s, t) with s = sin(x) and 177 | * c = cos(x). 178 | * @param x in degrees. 179 | *

180 | * The results obey exactly the elementary properties of the trigonometric 181 | * functions, e.g., sin 9° = cos 81° = − sin 123456789°. 182 | **********************************************************************/ 183 | public static void sincosd(Pair p, double x) { 184 | // In order to minimize round-off errors, this function exactly reduces 185 | // the argument to the range [-45, 45] before converting it to radians. 186 | double r; int q; 187 | r = x % 360.0; 188 | q = (int)Math.round(r / 90); // If r is NaN this returns 0 189 | r -= 90 * q; 190 | // now abs(r) <= 45 191 | r = Math.toRadians(r); 192 | // Possibly could call the gnu extension sincos 193 | double s = Math.sin(r), c = Math.cos(r); 194 | double sinx, cosx; 195 | switch (q & 3) { 196 | case 0: sinx = s; cosx = c; break; 197 | case 1: sinx = c; cosx = -s; break; 198 | case 2: sinx = -s; cosx = -c; break; 199 | default: sinx = -c; cosx = s; break; // case 3 200 | } 201 | if (sinx == 0) sinx = Math.copySign(sinx, x); 202 | p.first = sinx; p.second = 0.0 + cosx; 203 | } 204 | 205 | /** 206 | * Evaluate the sine and cosine function with reduced argument plus correction 207 | * 208 | * @param p return Pair(s, t) with s = 209 | * sin(x + t) and c = cos(x + t). 210 | * @param x reduced angle in degrees. 211 | * @param t correction in degrees. 212 | *

213 | * This is a variant of GeoMath.sincosd allowing a correction to the angle to 214 | * be supplied. x x must be in [−180°, 180°] and 215 | * t is assumed to be a small correction. GeoMath.AngRound is 216 | * applied to the reduced angle to prevent problems with x + t 217 | * being extremely close but not exactly equal to one of the four cardinal 218 | * directions. 219 | **********************************************************************/ 220 | public static void sincosde(Pair p, double x, double t) { 221 | // In order to minimize round-off errors, this function exactly reduces 222 | // the argument to the range [-45, 45] before converting it to radians. 223 | double r; int q; 224 | q = (int)Math.round(x / 90); // If r is NaN this returns 0 225 | r = x - 90 * q; 226 | // now abs(r) <= 45 227 | r = Math.toRadians(GeoMath.AngRound(r + t)); 228 | // Possibly could call the gnu extension sincos 229 | double s = Math.sin(r), c = Math.cos(r); 230 | double sinx, cosx; 231 | switch (q & 3) { 232 | case 0: sinx = s; cosx = c; break; 233 | case 1: sinx = c; cosx = -s; break; 234 | case 2: sinx = -s; cosx = -c; break; 235 | default: sinx = -c; cosx = s; break; // case 3 236 | } 237 | if (sinx == 0) sinx = Math.copySign(sinx, x); 238 | p.first = sinx; p.second = 0.0 + cosx; 239 | } 240 | 241 | /** 242 | * Evaluate the atan2 function with the result in degrees 243 | * 244 | * @param y the sine of the angle 245 | * @param x the cosine of the angle 246 | * @return atan2(y, x) in degrees. 247 | *

248 | * The result is in the range [−180° 180°]. N.B., 249 | * atan2d(±0, −1) = ±180°. 250 | **********************************************************************/ 251 | public static double atan2d(double y, double x) { 252 | // In order to minimize round-off errors, this function rearranges the 253 | // arguments so that result of atan2 is in the range [-pi/4, pi/4] before 254 | // converting it to degrees and mapping the result to the correct 255 | // quadrant. 256 | int q = 0; 257 | if (Math.abs(y) > Math.abs(x)) { double t; t = x; x = y; y = t; q = 2; } 258 | if (x < 0) { x = -x; ++q; } 259 | // here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4] 260 | double ang = Math.toDegrees(Math.atan2(y, x)); 261 | switch (q) { 262 | // Note that atan2d(-0.0, 1.0) will return -0. However, we expect that 263 | // atan2d will not be called with y = -0. If need be, include 264 | // 265 | // case 0: ang = 0 + ang; break; 266 | // 267 | // and handle mpfr as in AngRound. 268 | case 1: ang = Math.copySign(180.0, y) - ang; break; 269 | case 2: ang = 90 - ang; break; 270 | case 3: ang = -90 + ang; break; 271 | default: break; 272 | } 273 | return ang; 274 | } 275 | 276 | private GeoMath() {} 277 | } 278 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/GeodesicData.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.GeodesicData class 3 | * 4 | * Copyright (c) Charles Karney (2013) and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * The results of geodesic calculations. 12 | * 13 | * This is used to return the results for a geodesic between point 1 14 | * (lat1, lon1) and point 2 (lat2, lon2). Fields 15 | * that have not been set will be filled with Double.NaN. The returned 16 | * GeodesicData objects always include the parameters provided to {@link 17 | * Geodesic#Direct(double, double, double, double) Geodesic.Direct} and {@link 18 | * Geodesic#Inverse(double, double, double, double) Geodesic.Inverse} and it 19 | * always includes the field a12. 20 | **********************************************************************/ 21 | public class GeodesicData { 22 | /** 23 | * latitude of point 1 (degrees). 24 | **********************************************************************/ 25 | public double lat1; 26 | /** 27 | * longitude of point 1 (degrees). 28 | **********************************************************************/ 29 | public double lon1; 30 | /** 31 | * azimuth at point 1 (degrees). 32 | **********************************************************************/ 33 | public double azi1; 34 | /** 35 | * latitude of point 2 (degrees). 36 | **********************************************************************/ 37 | public double lat2; 38 | /** 39 | * longitude of point 2 (degrees). 40 | **********************************************************************/ 41 | public double lon2; 42 | /** 43 | * azimuth at point 2 (degrees). 44 | **********************************************************************/ 45 | public double azi2; 46 | /** 47 | * distance between point 1 and point 2 (meters). 48 | **********************************************************************/ 49 | public double s12; 50 | /** 51 | * arc length on the auxiliary sphere between point 1 and point 2 52 | * (degrees). 53 | **********************************************************************/ 54 | public double a12; 55 | /** 56 | * reduced length of geodesic (meters). 57 | **********************************************************************/ 58 | public double m12; 59 | /** 60 | * geodesic scale of point 2 relative to point 1 (dimensionless). 61 | **********************************************************************/ 62 | public double M12; 63 | /** 64 | * geodesic scale of point 1 relative to point 2 (dimensionless). 65 | **********************************************************************/ 66 | public double M21; 67 | /** 68 | * area under the geodesic (meters2). 69 | **********************************************************************/ 70 | public double S12; 71 | /** 72 | * Initialize all the fields to Double.NaN. 73 | **********************************************************************/ 74 | public GeodesicData() { 75 | lat1 = lon1 = azi1 = lat2 = lon2 = azi2 = 76 | s12 = a12 = m12 = M12 = M21 = S12 = Double.NaN; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/GeodesicLine.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.GeodesicLine class 3 | * 4 | * Copyright (c) Charles Karney (2013-2022) and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * A geodesic line. 12 | *

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

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

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

42 | * The algorithms are described in 43 | *

51 | *

52 | * Here's an example of using this class 53 | *

 54 |  * {@code
 55 |  * import net.sf.geographiclib.*;
 56 |  * public class GeodesicLineTest {
 57 |  *   public static void main(String[] args) {
 58 |  *     // Print waypoints between JFK and SIN
 59 |  *     Geodesic geod = Geodesic.WGS84;
 60 |  *     double
 61 |  *       lat1 = 40.640, lon1 = -73.779, // JFK
 62 |  *       lat2 =  1.359, lon2 = 103.989; // SIN
 63 |  *     GeodesicLine line = geod.InverseLine(lat1, lon1, lat2, lon2,
 64 |  *                                          GeodesicMask.DISTANCE_IN |
 65 |  *                                          GeodesicMask.LATITUDE |
 66 |  *                                          GeodesicMask.LONGITUDE);
 67 |  *     double ds0 = 500e3;     // Nominal distance between points = 500 km
 68 |  *     // The number of intervals
 69 |  *     int num = (int)(Math.ceil(line.Distance() / ds0));
 70 |  *     {
 71 |  *       // Use intervals of equal length
 72 |  *       double ds = line.Distance() / num;
 73 |  *       for (int i = 0; i <= num; ++i) {
 74 |  *         GeodesicData g = line.Position(i * ds,
 75 |  *                                        GeodesicMask.LATITUDE |
 76 |  *                                        GeodesicMask.LONGITUDE);
 77 |  *         System.out.println(i + " " + g.lat2 + " " + g.lon2);
 78 |  *       }
 79 |  *     }
 80 |  *     {
 81 |  *       // Slightly faster, use intervals of equal arc length
 82 |  *       double da = line.Arc() / num;
 83 |  *       for (int i = 0; i <= num; ++i) {
 84 |  *         GeodesicData g = line.ArcPosition(i * da,
 85 |  *                                           GeodesicMask.LATITUDE |
 86 |  *                                           GeodesicMask.LONGITUDE);
 87 |  *         System.out.println(i + " " + g.lat2 + " " + g.lon2);
 88 |  *       }
 89 |  *     }
 90 |  *   }
 91 |  * }}
92 | **********************************************************************/ 93 | public class GeodesicLine { 94 | 95 | private static final int nC1_ = Geodesic.nC1_; 96 | private static final int nC1p_ = Geodesic.nC1p_; 97 | private static final int nC2_ = Geodesic.nC2_; 98 | private static final int nC3_ = Geodesic.nC3_; 99 | private static final int nC4_ = Geodesic.nC4_; 100 | 101 | private double _lat1, _lon1, _azi1; 102 | private double _a, _f, _b, _c2, _f1, _salp0, _calp0, _k2, 103 | _salp1, _calp1, _ssig1, _csig1, _dn1, _stau1, _ctau1, _somg1, _comg1, 104 | _A1m1, _A2m1, _A3c, _B11, _B21, _B31, _A4, _B41; 105 | private double _a13, _s13; 106 | // index zero elements of _C1a, _C1pa, _C2a, _C3a are unused 107 | private double _C1a[], _C1pa[], _C2a[], _C3a[], 108 | _C4a[]; // all the elements of _C4a are used 109 | private int _caps; 110 | 111 | /** 112 | * Constructor for a geodesic line staring at latitude lat1, longitude 113 | * lon1, and azimuth azi1 (all in degrees). 114 | *

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

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

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

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

147 | * The {@link GeodesicMask} values are 148 | *

176 | **********************************************************************/ 177 | public GeodesicLine(Geodesic g, 178 | double lat1, double lon1, double azi1, 179 | int caps) { 180 | azi1 = GeoMath.AngNormalize(azi1); 181 | double salp1, calp1; 182 | Pair p = new Pair(); 183 | // Guard against underflow in salp0 184 | GeoMath.sincosd(p, GeoMath.AngRound(azi1)); 185 | salp1 = p.first; calp1 = p.second; 186 | LineInit(g, lat1, lon1, azi1, salp1, calp1, caps, p); 187 | } 188 | 189 | private void LineInit(Geodesic g, 190 | double lat1, double lon1, 191 | double azi1, double salp1, double calp1, 192 | int caps, Pair p) { 193 | _a = g._a; 194 | _f = g._f; 195 | _b = g._b; 196 | _c2 = g._c2; 197 | _f1 = g._f1; 198 | // Always allow latitude and azimuth and unrolling the longitude 199 | _caps = caps | GeodesicMask.LATITUDE | GeodesicMask.AZIMUTH | 200 | GeodesicMask.LONG_UNROLL; 201 | 202 | _lat1 = GeoMath.LatFix(lat1); 203 | _lon1 = lon1; 204 | _azi1 = azi1; _salp1 = salp1; _calp1 = calp1; 205 | double cbet1, sbet1; 206 | GeoMath.sincosd(p, GeoMath.AngRound(_lat1)); 207 | sbet1 = _f1 * p.first; cbet1 = p.second; 208 | // Ensure cbet1 = +epsilon at poles 209 | GeoMath.norm(p, sbet1, cbet1); 210 | sbet1 = p.first; cbet1 = Math.max(Geodesic.tiny_, p.second); 211 | _dn1 = Math.sqrt(1 + g._ep2 * GeoMath.sq(sbet1)); 212 | 213 | // Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), 214 | _salp0 = _salp1 * cbet1; // alp0 in [0, pi/2 - |bet1|] 215 | // Alt: calp0 = Math.hypot(sbet1, calp1 * cbet1). The following 216 | // is slightly better (consider the case salp1 = 0). 217 | _calp0 = Math.hypot(_calp1, _salp1 * sbet1); 218 | // Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). 219 | // sig = 0 is nearest northward crossing of equator. 220 | // With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). 221 | // With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 222 | // With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 223 | // Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). 224 | // With alp0 in (0, pi/2], quadrants for sig and omg coincide. 225 | // No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. 226 | // With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. 227 | _ssig1 = sbet1; _somg1 = _salp0 * sbet1; 228 | _csig1 = _comg1 = sbet1 != 0 || _calp1 != 0 ? cbet1 * _calp1 : 1; 229 | GeoMath.norm(p, _ssig1, _csig1); 230 | _ssig1 = p.first; _csig1 = p.second; // sig1 in (-pi, pi] 231 | // GeoMath.norm(_somg1, _comg1); -- don't need to normalize! 232 | 233 | _k2 = GeoMath.sq(_calp0) * g._ep2; 234 | double eps = _k2 / (2 * (1 + Math.sqrt(1 + _k2)) + _k2); 235 | 236 | if ((_caps & GeodesicMask.CAP_C1) != 0) { 237 | _A1m1 = Geodesic.A1m1f(eps); 238 | _C1a = new double[nC1_ + 1]; 239 | Geodesic.C1f(eps, _C1a); 240 | _B11 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C1a); 241 | double s = Math.sin(_B11), c = Math.cos(_B11); 242 | // tau1 = sig1 + B11 243 | _stau1 = _ssig1 * c + _csig1 * s; 244 | _ctau1 = _csig1 * c - _ssig1 * s; 245 | // Not necessary because C1pa reverts C1a 246 | // _B11 = -SinCosSeries(true, _stau1, _ctau1, _C1pa, nC1p_); 247 | } 248 | 249 | if ((_caps & GeodesicMask.CAP_C1p) != 0) { 250 | _C1pa = new double[nC1p_ + 1]; 251 | Geodesic.C1pf(eps, _C1pa); 252 | } 253 | 254 | if ((_caps & GeodesicMask.CAP_C2) != 0) { 255 | _C2a = new double[nC2_ + 1]; 256 | _A2m1 = Geodesic.A2m1f(eps); 257 | Geodesic.C2f(eps, _C2a); 258 | _B21 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C2a); 259 | } 260 | 261 | if ((_caps & GeodesicMask.CAP_C3) != 0) { 262 | _C3a = new double[nC3_]; 263 | g.C3f(eps, _C3a); 264 | _A3c = -_f * _salp0 * g.A3f(eps); 265 | _B31 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C3a); 266 | } 267 | 268 | if ((_caps & GeodesicMask.CAP_C4) != 0) { 269 | _C4a = new double[nC4_]; 270 | g.C4f(eps, _C4a); 271 | // Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) 272 | _A4 = GeoMath.sq(_a) * _calp0 * _salp0 * g._e2; 273 | _B41 = Geodesic.SinCosSeries(false, _ssig1, _csig1, _C4a); 274 | } 275 | } 276 | 277 | protected GeodesicLine(Geodesic g, 278 | double lat1, double lon1, 279 | double azi1, double salp1, double calp1, 280 | int caps, boolean arcmode, double s13_a13) { 281 | Pair p = new Pair(); 282 | LineInit(g, lat1, lon1, azi1, salp1, calp1, caps, p); 283 | GenSetDistance(arcmode, s13_a13); 284 | } 285 | 286 | /** 287 | * A default constructor. If GeodesicLine.Position is called on the 288 | * resulting object, it returns immediately (without doing any calculations). 289 | * The object can be set with a call to {@link Geodesic.Line}. Use {@link 290 | * Init()} to test whether object is still in this uninitialized state. 291 | * (This constructor was useful in C++, e.g., to allow vectors of 292 | * GeodesicLine objects. It may not be needed in Java, so make it private.) 293 | **********************************************************************/ 294 | private GeodesicLine() { _caps = 0; } 295 | 296 | /** 297 | * Compute the position of point 2 which is a distance s12 (meters) 298 | * from point 1. 299 | *

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

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

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

320 | * @param s12 distance from point 1 to point 2 (meters); it can be 321 | * negative. 322 | * @param outmask a bitor'ed combination of {@link GeodesicMask} values 323 | * specifying which results should be returned. 324 | * @return a {@link GeodesicData} object including the requested results. 325 | *

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

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

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

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

364 | * @param a12 arc length from point 1 to point 2 (degrees); it can 365 | * be negative. 366 | * @param outmask a bitor'ed combination of {@link GeodesicMask} values 367 | * specifying which results should be returned. 368 | * @return a {@link GeodesicData} object giving lat1, lon2, 369 | * azi2, and a12. 370 | *

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

385 | * @param arcmode boolean flag determining the meaning of the second 386 | * parameter; if arcmode is false, then the GeodesicLine object must have 387 | * been constructed with caps |= {@link GeodesicMask#DISTANCE_IN}. 388 | * @param s12_a12 if arcmode is false, this is the distance between 389 | * point 1 and point 2 (meters); otherwise it is the arc length between 390 | * point 1 and point 2 (degrees); it can be negative. 391 | * @param outmask a bitor'ed combination of {@link GeodesicMask} values 392 | * specifying which results should be returned. 393 | * @return a {@link GeodesicData} object with the requested results. 394 | *

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

421 | *

422 | * Requesting a value which the GeodesicLine object is not capable of 423 | * computing is not an error; Double.NaN is returned instead. 424 | **********************************************************************/ 425 | public GeodesicData Position(boolean arcmode, double s12_a12, 426 | int outmask) { 427 | outmask &= _caps & GeodesicMask.OUT_MASK; 428 | GeodesicData r = new GeodesicData(); 429 | if (!( Init() && 430 | (arcmode || 431 | (_caps & (GeodesicMask.OUT_MASK & GeodesicMask.DISTANCE_IN)) != 0) 432 | )) 433 | // Uninitialized or impossible distance calculation requested 434 | return r; 435 | r.lat1 = _lat1; r.azi1 = _azi1; 436 | r.lon1 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) ? _lon1 : 437 | GeoMath.AngNormalize(_lon1); 438 | 439 | // Avoid warning about uninitialized B12. 440 | double sig12, ssig12, csig12, B12 = 0, AB1 = 0; 441 | if (arcmode) { 442 | // Interpret s12_a12 as spherical arc length 443 | r.a12 = s12_a12; 444 | sig12 = Math.toRadians(s12_a12); 445 | Pair p = new Pair(); 446 | GeoMath.sincosd(p, s12_a12); ssig12 = p.first; csig12 = p.second; 447 | } else { 448 | // Interpret s12_a12 as distance 449 | r.s12 = s12_a12; 450 | double 451 | tau12 = s12_a12 / (_b * (1 + _A1m1)), 452 | s = Math.sin(tau12), 453 | c = Math.cos(tau12); 454 | // tau2 = tau1 + tau12 455 | B12 = - Geodesic.SinCosSeries(true, 456 | _stau1 * c + _ctau1 * s, 457 | _ctau1 * c - _stau1 * s, 458 | _C1pa); 459 | sig12 = tau12 - (B12 - _B11); 460 | ssig12 = Math.sin(sig12); csig12 = Math.cos(sig12); 461 | if (Math.abs(_f) > 0.01) { 462 | // Reverted distance series is inaccurate for |f| > 1/100, so correct 463 | // sig12 with 1 Newton iteration. The following table shows the 464 | // approximate maximum error for a = WGS_a() and various f relative to 465 | // GeodesicExact. 466 | // erri = the error in the inverse solution (nm) 467 | // errd = the error in the direct solution (series only) (nm) 468 | // errda = the error in the direct solution 469 | // (series + 1 Newton) (nm) 470 | // 471 | // f erri errd errda 472 | // -1/5 12e6 1.2e9 69e6 473 | // -1/10 123e3 12e6 765e3 474 | // -1/20 1110 108e3 7155 475 | // -1/50 18.63 200.9 27.12 476 | // -1/100 18.63 23.78 23.37 477 | // -1/150 18.63 21.05 20.26 478 | // 1/150 22.35 24.73 25.83 479 | // 1/100 22.35 25.03 25.31 480 | // 1/50 29.80 231.9 30.44 481 | // 1/20 5376 146e3 10e3 482 | // 1/10 829e3 22e6 1.5e6 483 | // 1/5 157e6 3.8e9 280e6 484 | double 485 | ssig2 = _ssig1 * csig12 + _csig1 * ssig12, 486 | csig2 = _csig1 * csig12 - _ssig1 * ssig12; 487 | B12 = Geodesic.SinCosSeries(true, ssig2, csig2, _C1a); 488 | double serr = (1 + _A1m1) * (sig12 + (B12 - _B11)) - s12_a12 / _b; 489 | sig12 = sig12 - serr / Math.sqrt(1 + _k2 * GeoMath.sq(ssig2)); 490 | ssig12 = Math.sin(sig12); csig12 = Math.cos(sig12); 491 | // Update B12 below 492 | } 493 | r.a12 = Math.toDegrees(sig12); 494 | } 495 | 496 | double ssig2, csig2, sbet2, cbet2, salp2, calp2; 497 | // sig2 = sig1 + sig12 498 | ssig2 = _ssig1 * csig12 + _csig1 * ssig12; 499 | csig2 = _csig1 * csig12 - _ssig1 * ssig12; 500 | double dn2 = Math.sqrt(1 + _k2 * GeoMath.sq(ssig2)); 501 | if ((outmask & (GeodesicMask.DISTANCE | GeodesicMask.REDUCEDLENGTH | 502 | GeodesicMask.GEODESICSCALE)) != 0) { 503 | if (arcmode || Math.abs(_f) > 0.01) 504 | B12 = Geodesic.SinCosSeries(true, ssig2, csig2, _C1a); 505 | AB1 = (1 + _A1m1) * (B12 - _B11); 506 | } 507 | // sin(bet2) = cos(alp0) * sin(sig2) 508 | sbet2 = _calp0 * ssig2; 509 | // Alt: cbet2 = Math.hypot(csig2, salp0 * ssig2); 510 | cbet2 = Math.hypot(_salp0, _calp0 * csig2); 511 | if (cbet2 == 0) 512 | // I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case 513 | cbet2 = csig2 = Geodesic.tiny_; 514 | // tan(alp0) = cos(sig2)*tan(alp2) 515 | salp2 = _salp0; calp2 = _calp0 * csig2; // No need to normalize 516 | 517 | if ((outmask & GeodesicMask.DISTANCE) != 0 && arcmode) 518 | r.s12 = _b * ((1 + _A1m1) * sig12 + AB1); 519 | 520 | if ((outmask & GeodesicMask.LONGITUDE) != 0) { 521 | // tan(omg2) = sin(alp0) * tan(sig2) 522 | double somg2 = _salp0 * ssig2, comg2 = csig2, // No need to normalize 523 | E = Math.copySign(1, _salp0); // east or west going? 524 | // omg12 = omg2 - omg1 525 | double omg12 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) 526 | ? E * (sig12 527 | - (Math.atan2( ssig2, csig2) - Math.atan2( _ssig1, _csig1)) 528 | + (Math.atan2(E*somg2, comg2) - Math.atan2(E*_somg1, _comg1))) 529 | : Math.atan2(somg2 * _comg1 - comg2 * _somg1, 530 | comg2 * _comg1 + somg2 * _somg1); 531 | double lam12 = omg12 + _A3c * 532 | ( sig12 + (Geodesic.SinCosSeries(true, ssig2, csig2, _C3a) 533 | - _B31)); 534 | double lon12 = Math.toDegrees(lam12); 535 | r.lon2 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) ? _lon1 + lon12 : 536 | GeoMath.AngNormalize(r.lon1 + GeoMath.AngNormalize(lon12)); 537 | } 538 | 539 | if ((outmask & GeodesicMask.LATITUDE) != 0) 540 | r.lat2 = GeoMath.atan2d(sbet2, _f1 * cbet2); 541 | 542 | if ((outmask & GeodesicMask.AZIMUTH) != 0) 543 | r.azi2 = GeoMath.atan2d(salp2, calp2); 544 | 545 | if ((outmask & 546 | (GeodesicMask.REDUCEDLENGTH | GeodesicMask.GEODESICSCALE)) != 0) { 547 | double 548 | B22 = Geodesic.SinCosSeries(true, ssig2, csig2, _C2a), 549 | AB2 = (1 + _A2m1) * (B22 - _B21), 550 | J12 = (_A1m1 - _A2m1) * sig12 + (AB1 - AB2); 551 | if ((outmask & GeodesicMask.REDUCEDLENGTH) != 0) 552 | // Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure 553 | // accurate cancellation in the case of coincident points. 554 | r.m12 = _b * ((dn2 * (_csig1 * ssig2) - _dn1 * (_ssig1 * csig2)) 555 | - _csig1 * csig2 * J12); 556 | if ((outmask & GeodesicMask.GEODESICSCALE) != 0) { 557 | double t = _k2 * (ssig2 - _ssig1) * (ssig2 + _ssig1) / (_dn1 + dn2); 558 | r.M12 = csig12 + (t * ssig2 - csig2 * J12) * _ssig1 / _dn1; 559 | r.M21 = csig12 - (t * _ssig1 - _csig1 * J12) * ssig2 / dn2; 560 | } 561 | } 562 | 563 | if ((outmask & GeodesicMask.AREA) != 0) { 564 | double 565 | B42 = Geodesic.SinCosSeries(false, ssig2, csig2, _C4a); 566 | double salp12, calp12; 567 | if (_calp0 == 0 || _salp0 == 0) { 568 | // alp12 = alp2 - alp1, used in atan2 so no need to normalize 569 | salp12 = salp2 * _calp1 - calp2 * _salp1; 570 | calp12 = calp2 * _calp1 + salp2 * _salp1; 571 | } else { 572 | // tan(alp) = tan(alp0) * sec(sig) 573 | // tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) 574 | // = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) 575 | // If csig12 > 0, write 576 | // csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) 577 | // else 578 | // csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 579 | // No need to normalize 580 | salp12 = _calp0 * _salp0 * 581 | (csig12 <= 0 ? _csig1 * (1 - csig12) + ssig12 * _ssig1 : 582 | ssig12 * (_csig1 * ssig12 / (1 + csig12) + _ssig1)); 583 | calp12 = GeoMath.sq(_salp0) + GeoMath.sq(_calp0) * _csig1 * csig2; 584 | } 585 | r.S12 = _c2 * Math.atan2(salp12, calp12) + _A4 * (B42 - _B41); 586 | } 587 | 588 | return r; 589 | } 590 | 591 | /** 592 | * Specify position of point 3 in terms of distance. 593 | * 594 | * @param s13 the distance from point 1 to point 3 (meters); it 595 | * can be negative. 596 | * 597 | * This is only useful if the GeodesicLine object has been constructed 598 | * with caps |= {@link GeodesicMask#DISTANCE_IN}. 599 | **********************************************************************/ 600 | public void SetDistance(double s13) { 601 | _s13 = s13; 602 | GeodesicData g = Position(false, _s13, 0); 603 | _a13 = g.a12; 604 | } 605 | 606 | /** 607 | * Specify position of point 3 in terms of arc length. 608 | * 609 | * @param a13 the arc length from point 1 to point 3 (degrees); it 610 | * can be negative. 611 | * 612 | * The distance s13 is only set if the GeodesicLine object has been 613 | * constructed with caps |= {@link GeodesicMask#DISTANCE}. 614 | **********************************************************************/ 615 | void SetArc(double a13) { 616 | _a13 = a13; 617 | GeodesicData g = Position(true, _a13, GeodesicMask.DISTANCE); 618 | _s13 = g.s12; 619 | } 620 | 621 | /** 622 | * Specify position of point 3 in terms of either distance or arc length. 623 | * 624 | * @param arcmode boolean flag determining the meaning of the second 625 | * parameter; if arcmode is false, then the GeodesicLine object must 626 | * have been constructed with caps |= 627 | * {@link GeodesicMask#DISTANCE_IN}. 628 | * @param s13_a13 if arcmode is false, this is the distance from 629 | * point 1 to point 3 (meters); otherwise it is the arc length from 630 | * point 1 to point 3 (degrees); it can be negative. 631 | **********************************************************************/ 632 | public void GenSetDistance(boolean arcmode, double s13_a13) { 633 | if (arcmode) 634 | SetArc(s13_a13); 635 | else 636 | SetDistance(s13_a13); 637 | } 638 | 639 | /** 640 | * @return true if the object has been initialized. 641 | **********************************************************************/ 642 | private boolean Init() { return _caps != 0; } 643 | 644 | /** 645 | * @return lat1 the latitude of point 1 (degrees). 646 | **********************************************************************/ 647 | public double Latitude() 648 | { return Init() ? _lat1 : Double.NaN; } 649 | 650 | /** 651 | * @return lon1 the longitude of point 1 (degrees). 652 | **********************************************************************/ 653 | public double Longitude() 654 | { return Init() ? _lon1 : Double.NaN; } 655 | 656 | /** 657 | * @return azi1 the azimuth (degrees) of the geodesic line at point 1. 658 | **********************************************************************/ 659 | public double Azimuth() 660 | { return Init() ? _azi1 : Double.NaN; } 661 | 662 | /** 663 | * @return pair of sine and cosine of azi1 the azimuth (degrees) of 664 | * the geodesic line at point 1. 665 | **********************************************************************/ 666 | public Pair AzimuthCosines() { 667 | return new Pair(Init() ? _salp1 : Double.NaN, 668 | Init() ? _calp1 : Double.NaN); 669 | } 670 | 671 | /** 672 | * @return azi0 the azimuth (degrees) of the geodesic line as it 673 | * crosses the equator in a northward direction. 674 | **********************************************************************/ 675 | public double EquatorialAzimuth() { 676 | return Init() ? 677 | GeoMath.atan2d(_salp0, _calp0) : Double.NaN; 678 | } 679 | 680 | /** 681 | * @return pair of sine and cosine of azi0 the azimuth of the geodesic 682 | * line as it crosses the equator in a northward direction. 683 | **********************************************************************/ 684 | public Pair EquatorialAzimuthCosines() { 685 | return new Pair(Init() ? _salp0 : Double.NaN, 686 | Init() ? _calp0 : Double.NaN); 687 | } 688 | 689 | /** 690 | * @return a1 the arc length (degrees) between the northward 691 | * equatorial crossing and point 1. 692 | **********************************************************************/ 693 | public double EquatorialArc() { 694 | return Init() ? 695 | GeoMath.atan2d(_ssig1, _csig1) : Double.NaN; 696 | } 697 | 698 | /** 699 | * @return a the equatorial radius of the ellipsoid (meters). This is 700 | * the value inherited from the Geodesic object used in the constructor. 701 | **********************************************************************/ 702 | public double EquatorialRadius() 703 | { return Init() ? _a : Double.NaN; } 704 | 705 | /** 706 | * @return f the flattening of the ellipsoid. This is the value 707 | * inherited from the Geodesic object used in the constructor. 708 | **********************************************************************/ 709 | public double Flattening() 710 | { return Init() ? _f : Double.NaN; } 711 | 712 | /** 713 | * @return caps the computational capabilities that this object was 714 | * constructed with. LATITUDE and AZIMUTH are always included. 715 | **********************************************************************/ 716 | public int Capabilities() { return _caps; } 717 | 718 | /** 719 | * @param testcaps a set of bitor'ed {@link GeodesicMask} values. 720 | * @return true if the GeodesicLine object has all these capabilities. 721 | **********************************************************************/ 722 | public boolean Capabilities(int testcaps) { 723 | testcaps &= GeodesicMask.OUT_ALL; 724 | return (_caps & testcaps) == testcaps; 725 | } 726 | 727 | /** 728 | * The distance or arc length to point 3. 729 | * 730 | * @param arcmode boolean flag determining the meaning of returned 731 | * value. 732 | * @return s13 if arcmode is false; a13 if 733 | * arcmode is true. 734 | **********************************************************************/ 735 | public double GenDistance(boolean arcmode) 736 | { return Init() ? (arcmode ? _a13 : _s13) : Double.NaN; } 737 | 738 | /** 739 | * @return s13, the distance to point 3 (meters). 740 | **********************************************************************/ 741 | public double Distance() { return GenDistance(false); } 742 | 743 | /** 744 | * @return a13, the arc length to point 3 (degrees). 745 | **********************************************************************/ 746 | public double Arc() { return GenDistance(true); } 747 | } 748 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/GeodesicMask.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.GeodesicMask class 3 | * 4 | * Copyright (c) Charles Karney (2013-2014) and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * Bit masks for what geodesic calculations to do. 12 | *

13 | * These masks do double duty. They specify (via the outmask parameter) 14 | * which results to return in the {@link GeodesicData} object returned by the 15 | * general routines {@link Geodesic#Direct(double, double, double, double, int) 16 | * Geodesic.Direct} and {@link Geodesic#Inverse(double, double, double, double, 17 | * int) Geodesic.Inverse} routines. They also signify (via the caps 18 | * parameter) to the {@link GeodesicLine#GeodesicLine(Geodesic, double, double, 19 | * double, int) GeodesicLine.GeodesicLine} constructor and to {@link 20 | * Geodesic#Line(double, double, double, int) Geodesic.Line} what capabilities 21 | * should be included in the {@link GeodesicLine} object. 22 | **********************************************************************/ 23 | public class GeodesicMask { 24 | protected static final int CAP_NONE = 0; 25 | protected static final int CAP_C1 = 1<<0; 26 | protected static final int CAP_C1p = 1<<1; 27 | protected static final int CAP_C2 = 1<<2; 28 | protected static final int CAP_C3 = 1<<3; 29 | protected static final int CAP_C4 = 1<<4; 30 | protected static final int CAP_ALL = 0x1F; 31 | protected static final int CAP_MASK = CAP_ALL; 32 | protected static final int OUT_ALL = 0x7F80; 33 | protected static final int OUT_MASK = 0xFF80; // Include LONG_UNROLL 34 | 35 | /** 36 | * No capabilities, no output. 37 | **********************************************************************/ 38 | public static final int NONE = 0; 39 | /** 40 | * Calculate latitude lat2. (It's not necessary to include this as a 41 | * capability to {@link GeodesicLine} because this is included by default.) 42 | **********************************************************************/ 43 | public static final int LATITUDE = 1<<7 | CAP_NONE; 44 | /** 45 | * Calculate longitude lon2. 46 | **********************************************************************/ 47 | public static final int LONGITUDE = 1<<8 | CAP_C3; 48 | /** 49 | * Calculate azimuths azi1 and azi2. (It's not necessary to 50 | * include this as a capability to {@link GeodesicLine} because this is 51 | * included by default.) 52 | **********************************************************************/ 53 | public static final int AZIMUTH = 1<<9 | CAP_NONE; 54 | /** 55 | * Calculate distance s12. 56 | **********************************************************************/ 57 | public static final int DISTANCE = 1<<10 | CAP_C1; 58 | /** 59 | * All of the above, the "standard" output and capabilities. 60 | **********************************************************************/ 61 | public static final int STANDARD = LATITUDE | LONGITUDE | 62 | AZIMUTH | DISTANCE; 63 | /** 64 | * Allow distance s12 to be used as input in the direct 65 | * geodesic problem. 66 | **********************************************************************/ 67 | public static final int DISTANCE_IN = 1<<11 | CAP_C1 | CAP_C1p; 68 | /** 69 | * Calculate reduced length m12. 70 | **********************************************************************/ 71 | public static final int REDUCEDLENGTH = 1<<12 | CAP_C1 | CAP_C2; 72 | /** 73 | * Calculate geodesic scales M12 and M21. 74 | **********************************************************************/ 75 | public static final int GEODESICSCALE = 1<<13 | CAP_C1 | CAP_C2; 76 | /** 77 | * Calculate area S12. 78 | **********************************************************************/ 79 | public static final int AREA = 1<<14 | CAP_C4; 80 | /** 81 | * All capabilities, calculate everything. (LONG_UNROLL is not included in 82 | * this mask.) 83 | **********************************************************************/ 84 | public static final int ALL = OUT_ALL| CAP_ALL; 85 | /** 86 | * Unroll lon2. 87 | **********************************************************************/ 88 | public static final int LONG_UNROLL = 1<<15; 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/GeographicErr.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.GeographicErr class 3 | * 4 | * Copyright (c) Charles Karney (2013) and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * Exception handling for GeographicLib. 12 | *

13 | * A class to handle exceptions. It's derived from RuntimeException so it 14 | * can be caught by the usual catch clauses. 15 | **********************************************************************/ 16 | public class GeographicErr extends RuntimeException { 17 | /** 18 | * Constructor 19 | *

20 | * @param msg a string message, which is accessible in the catch 21 | * clause via getMessage(). 22 | **********************************************************************/ 23 | public GeographicErr(String msg) { super(msg); } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/Gnomonic.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.Gnomonic class 3 | * 4 | * Copyright (c) BMW Car IT GmbH (2014-2019) 5 | * and Charles Karney (2020-2022) and licensed and 6 | * licensed under the MIT/X11 License. For more information, see 7 | * https://geographiclib.sourceforge.io/ 8 | **********************************************************************/ 9 | package net.sf.geographiclib; 10 | 11 | /** 12 | * Gnomonic projection. 13 | *

14 | * Note: Gnomonic.java has been ported to Java from its C++ equivalent 15 | * Gnomonic.cpp, authored by C. F. F. Karney and licensed under MIT/X11 16 | * license. The following documentation is mostly the same as for its C++ 17 | * equivalent, but has been adopted to apply to this Java implementation. 18 | *

19 | * Gnomonic projection centered at an arbitrary position C on the 20 | * ellipsoid. This projection is derived in Section 8 of 21 | *

31 | *

32 | * The gnomonic projection of a point P on the ellipsoid is defined as 33 | * follows: compute the geodesic line from C to P; compute the 34 | * reduced length m12, geodesic scale M12, and ρ = 35 | * m12/M12; finally, this gives the coordinates x and 36 | * y of P in gnomonic projection with x = ρ sin 37 | * azi1; y = ρ cos azi1, where azi1 is the 38 | * azimuth of the geodesic at C. The method 39 | * {@link Gnomonic#Forward(double, double, double, double)} performs the 40 | * forward projection and 41 | * {@link Gnomonic#Reverse(double, double, double, double)} is the 42 | * inverse of the projection. The methods also return the azimuth 43 | * azi of the geodesic at P and reciprocal scale 44 | * rk in the azimuthal direction. The scale in the radial 45 | * direction is 1/rk2. 46 | *

47 | * For a sphere, ρ reduces to a tan(s12/a), where 48 | * s12 is the length of the geodesic from C to P, and the 49 | * gnomonic projection has the property that all geodesics appear as straight 50 | * lines. For an ellipsoid, this property holds only for geodesics interesting 51 | * the centers. However geodesic segments close to the center are approximately 52 | * straight. 53 | *

54 | * Consider a geodesic segment of length l. Let T be the point on 55 | * the geodesic (extended if necessary) closest to C, the center of the 56 | * projection, and t, be the distance CT. To lowest order, the 57 | * maximum deviation (as a true distance) of the corresponding gnomonic line 58 | * segment (i.e., with the same end points) from the geodesic is
59 | *
60 | * (K(T) − K(C)) 61 | * l2 t / 32. 62 | *
63 | *
64 | * where K is the Gaussian curvature. 65 | *

66 | * This result applies for any surface. For an ellipsoid of revolution, 67 | * consider all geodesics whose end points are within a distance r of 68 | * C. For a given r, the deviation is maximum when the latitude 69 | * of C is 45°, when endpoints are a distance r away, and 70 | * when their azimuths from the center are ± 45° or ± 71 | * 135°. To lowest order in r and the flattening f, the 72 | * deviation is f (r/2a)3 r. 73 | *

74 | * CAUTION: The definition of this projection for a sphere is standard. 75 | * However, there is no standard for how it should be extended to an ellipsoid. 76 | * The choices are: 77 | *

104 | *

105 | * Example of use: 106 | * 107 | *

108 |  * // Example of using the Gnomonic.java class
109 |  * import net.sf.geographiclib.Geodesic;
110 |  * import net.sf.geographiclib.Gnomonic;
111 |  * import net.sf.geographiclib.GnomonicData;
112 |  * public class ExampleGnomonic {
113 |  *   public static void main(String[] args) {
114 |  *     Geodesic geod = Geodesic.WGS84;
115 |  *     double lat0 = 48 + 50 / 60.0, lon0 = 2 + 20 / 60.0; // Paris
116 |  *     Gnomonic gnom = new Gnomonic(geod);
117 |  *     {
118 |  *       // Sample forward calculation
119 |  *       double lat = 50.9, lon = 1.8; // Calais
120 |  *       GnomonicData proj = gnom.Forward(lat0, lon0, lat, lon);
121 |  *       System.out.println(proj.x + " " + proj.y);
122 |  *     }
123 |  *     {
124 |  *       // Sample reverse calculation
125 |  *       double x = -38e3, y = 230e3;
126 |  *       GnomonicData proj = gnom.Reverse(lat0, lon0, x, y);
127 |  *       System.out.println(proj.lat + " " + proj.lon);
128 |  *     }
129 |  *   }
130 |  * }
131 |  * 
132 | */ 133 | 134 | public class Gnomonic { 135 | private static final double eps_ = 0.01 * Math.sqrt(Math.ulp(1.0)); 136 | private static final int numit_ = 10; 137 | private Geodesic _earth; 138 | private double _a, _f; 139 | 140 | /** 141 | * Constructor for Gnomonic. 142 | *

143 | * @param earth the {@link Geodesic} object to use for geodesic 144 | * calculations. 145 | */ 146 | public Gnomonic(Geodesic earth) { 147 | _earth = earth; 148 | _a = _earth.EquatorialRadius(); 149 | _f = _earth.Flattening(); 150 | } 151 | 152 | /** 153 | * Forward projection, from geographic to gnomonic. 154 | *

155 | * @param lat0 latitude of center point of projection (degrees). 156 | * @param lon0 longitude of center point of projection (degrees). 157 | * @param lat latitude of point (degrees). 158 | * @param lon longitude of point (degrees). 159 | * @return {@link GnomonicData} object with the following fields: 160 | * lat0, lon0, lat, lon, x, y, 161 | * azi, rk. 162 | *

163 | * lat0 and lat should be in the range [−90°, 164 | * 90°] and lon0 and lon should be in the range 165 | * [−540°, 540°). The scale of the projection is 166 | * 1/rk2 in the "radial" direction, azi clockwise 167 | * from true north, and is 1/rk in the direction perpendicular to 168 | * this. If the point lies "over the horizon", i.e., if rk ≤ 0, 169 | * then NaNs are returned for x and y (the correct values are 170 | * returned for azi and rk). A call to Forward followed by a 171 | * call to Reverse will return the original (lat, lon) (to 172 | * within roundoff) provided the point in not over the horizon. 173 | */ 174 | public GnomonicData Forward(double lat0, double lon0, double lat, double lon) 175 | { 176 | GeodesicData inv = 177 | _earth.Inverse(lat0, lon0, lat, lon, 178 | GeodesicMask.AZIMUTH | GeodesicMask.GEODESICSCALE | 179 | GeodesicMask.REDUCEDLENGTH); 180 | GnomonicData fwd = 181 | new GnomonicData(lat0, lon0, lat, lon, Double.NaN, Double.NaN, 182 | inv.azi2, inv.M12); 183 | 184 | if (inv.M12 > 0) { 185 | double rho = inv.m12 / inv.M12; 186 | Pair p = new Pair(); 187 | GeoMath.sincosd(p, inv.azi1); 188 | fwd.x = rho * p.first; 189 | fwd.y = rho * p.second; 190 | } 191 | 192 | return fwd; 193 | } 194 | 195 | /** 196 | * Reverse projection, from gnomonic to geographic. 197 | *

198 | * @param lat0 latitude of center point of projection (degrees). 199 | * @param lon0 longitude of center point of projection (degrees). 200 | * @param x easting of point (meters). 201 | * @param y northing of point (meters). 202 | * @return {@link GnomonicData} object with the following fields: 203 | * lat0, lon0, lat, lon, x, y, 204 | * azi, rk. 205 | *

206 | * lat0 should be in the range [−90°, 90°] and 207 | * lon0 should be in the range [−540°, 540°). 208 | * lat will be in the range [−90°, 90°] and lon 209 | * will be in the range [−180°, 180°]. The scale of the 210 | * projection is 1/rk2 in the "radial" direction, 211 | * azi clockwise from true north, and is 1/rk in the direction 212 | * perpendicular to this. Even though all inputs should return a valid 213 | * lat and lon, it's possible that the procedure fails to 214 | * converge for very large x or y; in this case NaNs are 215 | * returned for all the output arguments. A call to Reverse followed by a 216 | * call to Forward will return the original (x, y) (to 217 | * roundoff). 218 | */ 219 | public GnomonicData Reverse(double lat0, double lon0, double x, double y) { 220 | GnomonicData rev = 221 | new GnomonicData(lat0, lon0, Double.NaN, Double.NaN, x, y, Double.NaN, 222 | Double.NaN); 223 | 224 | double azi0 = GeoMath.atan2d(x, y); 225 | double rho = Math.hypot(x, y); 226 | double s = _a * Math.atan(rho / _a); 227 | boolean little = rho <= _a; 228 | 229 | if (!little) 230 | rho = 1 / rho; 231 | 232 | GeodesicLine line = 233 | _earth.Line(lat0, lon0, azi0, GeodesicMask.LATITUDE 234 | | GeodesicMask.LONGITUDE | GeodesicMask.AZIMUTH 235 | | GeodesicMask.DISTANCE_IN | GeodesicMask.REDUCEDLENGTH 236 | | GeodesicMask.GEODESICSCALE); 237 | 238 | int count = numit_, trip = 0; 239 | GeodesicData pos = null; 240 | 241 | while (count-- > 0) { 242 | pos = 243 | line.Position(s, GeodesicMask.LONGITUDE | GeodesicMask.LATITUDE 244 | | GeodesicMask.AZIMUTH | GeodesicMask.DISTANCE_IN 245 | | GeodesicMask.REDUCEDLENGTH 246 | | GeodesicMask.GEODESICSCALE); 247 | 248 | if (trip > 0) 249 | break; 250 | 251 | double ds = 252 | little ? ((pos.m12 / pos.M12) - rho) * pos.M12 * pos.M12 253 | : (rho - (pos.M12 / pos.m12)) * pos.m12 * pos.m12; 254 | s -= ds; 255 | 256 | if (Math.abs(ds) <= eps_ * _a) 257 | trip++; 258 | } 259 | 260 | if (trip == 0) 261 | return rev; 262 | 263 | rev.lat = pos.lat2; 264 | rev.lon = pos.lon2; 265 | rev.azi = pos.azi2; 266 | rev.rk = pos.M12; 267 | 268 | return rev; 269 | } 270 | 271 | /** 272 | * @return a the equatorial radius of the ellipsoid (meters). This is 273 | * the value inherited from the Geodesic object used in the constructor. 274 | **********************************************************************/ 275 | public double EquatorialRadius() { return _a; } 276 | 277 | /** 278 | * @return f the flattening of the ellipsoid. This is 279 | * the value inherited from the Geodesic object used in the constructor. 280 | **********************************************************************/ 281 | public double Flattening() { return _f; } 282 | } 283 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/GnomonicData.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.GnomonicData class 3 | * 4 | * Copyright (c) BMW Car IT GmbH (2014-2016) 5 | * and licensed under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * The results of gnomonic projection. 12 | *

13 | 14 | * This is used to return the results for a gnomonic projection of a point 15 | * (lat, lon) given a center point of projection (lat0, 16 | * lon0). The returned GnomonicData objects always include the 17 | * parameters provided to 18 | * {@link Gnomonic#Forward Gnomonic.Forward} 19 | * and 20 | * {@link Gnomonic#Reverse Gnomonic.Reverse} 21 | * and it always includes the fields x, y, azi. and 22 | * rk. 23 | **********************************************************************/ 24 | public class GnomonicData { 25 | /** 26 | * latitude of center point of projection (degrees). 27 | **********************************************************************/ 28 | public double lat0; 29 | /** 30 | * longitude of center point of projection (degrees). 31 | **********************************************************************/ 32 | public double lon0; 33 | /** 34 | * latitude of point (degrees). 35 | **********************************************************************/ 36 | public double lat; 37 | /** 38 | * longitude of point (degrees). 39 | **********************************************************************/ 40 | public double lon; 41 | /** 42 | * easting of point (meters). 43 | **********************************************************************/ 44 | public double x; 45 | /** 46 | * northing of point (meters). 47 | **********************************************************************/ 48 | public double y; 49 | /** 50 | * azimuth of geodesic at point (degrees). 51 | **********************************************************************/ 52 | public double azi; 53 | /** 54 | * reciprocal of azimuthal scale at point. 55 | **********************************************************************/ 56 | public double rk; 57 | 58 | /** 59 | * Initialize all the fields to Double.NaN. 60 | **********************************************************************/ 61 | public GnomonicData() { 62 | lat0 = lon0 = lat = lon = x = y = azi = rk = Double.NaN; 63 | } 64 | 65 | /** 66 | * Constructor initializing all the fields for gnomonic projection of a point 67 | * (lat, lon) given a center point of projection (lat0, 68 | * lon0). 69 | *

70 | * @param lat0 71 | * latitude of center point of projection (degrees). 72 | * @param lon0 73 | * longitude of center point of projection (degrees). 74 | * @param lat 75 | * latitude of point (degrees). 76 | * @param lon 77 | * longitude of point (degrees). 78 | * @param x 79 | * easting of point (meters). 80 | * @param y 81 | * northing of point (meters). 82 | * @param azi 83 | * azimuth of geodesic at point (degrees). 84 | * @param rk 85 | * reciprocal of azimuthal scale at point. 86 | */ 87 | public GnomonicData(double lat0, double lon0, double lat, double lon, 88 | double x, double y, double azi, double rk) { 89 | this.lat0 = lat0; 90 | this.lon0 = lon0; 91 | this.lat = lat; 92 | this.lon = lon; 93 | this.x = x; 94 | this.y = y; 95 | this.azi = azi; 96 | this.rk = rk; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/Pair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.Pair class 3 | * 4 | * Copyright (c) Charles Karney (2013-2020) ∑ and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * A pair of double precision numbers. 12 | *

13 | * This duplicates the C++ class {@code std::pair}. 14 | **********************************************************************/ 15 | public class Pair { 16 | /** 17 | * The first member of the pair. 18 | **********************************************************************/ 19 | public double first; 20 | /** 21 | * The second member of the pair. 22 | **********************************************************************/ 23 | public double second; 24 | /** 25 | * Constructor 26 | *

27 | * @param first the first member of the pair. 28 | * @param second the second member of the pair. 29 | **********************************************************************/ 30 | public Pair(double first, double second) 31 | { this.first = first; this.second = second; } 32 | /** 33 | * No-argument Constructor 34 | **********************************************************************/ 35 | public Pair() {} 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/PolygonArea.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.PolygonArea class 3 | * 4 | * Copyright (c) Charles Karney (2013-2022) and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * Polygon areas. 12 | *

13 | * This computes the area of a geodesic polygon using the method given 14 | * Section 6 of 15 | *

24 | *

25 | * Arbitrarily complex polygons are allowed. In the case self-intersecting of 26 | * polygons the area is accumulated "algebraically", e.g., the areas of the 2 27 | * loops in a figure-8 polygon will partially cancel. 28 | *

29 | * This class lets you add vertices one at a time to the polygon. The area 30 | * and perimeter are accumulated at two times the standard floating point 31 | * precision to guard against the loss of accuracy with many-sided polygons. 32 | * At any point you can ask for the perimeter and area so far. There's an 33 | * option to treat the points as defining a polyline instead of a polygon; in 34 | * that case, only the perimeter is computed. 35 | *

36 | * Example of use: 37 | *

 38 |  * {@code
 39 |  * // Compute the area of a geodesic polygon.
 40 |  *
 41 |  * // This program reads lines with lat, lon for each vertex of a polygon.
 42 |  * // At the end of input, the program prints the number of vertices,
 43 |  * // the perimeter of the polygon and its area (for the WGS84 ellipsoid).
 44 |  *
 45 |  * import java.util.*;
 46 |  * import net.sf.geographiclib.*;
 47 |  *
 48 |  * public class Planimeter {
 49 |  *   public static void main(String[] args) {
 50 |  *     PolygonArea p = new PolygonArea(Geodesic.WGS84, false);
 51 |  *     try {
 52 |  *       Scanner in = new Scanner(System.in);
 53 |  *       while (true) {
 54 |  *         double lat = in.nextDouble(), lon = in.nextDouble();
 55 |  *         p.AddPoint(lat, lon);
 56 |  *       }
 57 |  *     }
 58 |  *     catch (Exception e) {}
 59 |  *     PolygonResult r = p.Compute();
 60 |  *     System.out.println(r.num + " " + r.perimeter + " " + r.area);
 61 |  *   }
 62 |  * }}
63 | **********************************************************************/ 64 | public class PolygonArea { 65 | 66 | private Geodesic _earth; 67 | private double _area0; // Full ellipsoid area 68 | private boolean _polyline; // Assume polyline (don't close and skip area) 69 | private int _mask; 70 | private int _num; 71 | private int _crossings; 72 | private Accumulator _areasum, _perimetersum; 73 | private double _lat0, _lon0, _lat1, _lon1; 74 | private static int transit(double lon1, double lon2) { 75 | // Return 1 or -1 if crossing prime meridian in east or west direction. 76 | // Otherwise return zero. 77 | // Compute lon12 the same way as Geodesic.Inverse. 78 | Pair p = new Pair(); 79 | GeoMath.AngDiff(p, lon1, lon2); 80 | lon1 = GeoMath.AngNormalize(lon1); 81 | lon2 = GeoMath.AngNormalize(lon2); 82 | double lon12 = p.first; 83 | return 84 | lon12 > 0 && ((lon1 < 0 && lon2 >= 0) || 85 | (lon1 > 0 && lon2 == 0)) ? 1 : 86 | (lon12 < 0 && lon1 >= 0 && lon2 < 0 ? -1 : 0); 87 | } 88 | // an alternate version of transit to deal with longitudes in the direct 89 | // problem. 90 | private static int transitdirect(double lon1, double lon2) { 91 | // We want to compute exactly 92 | // int(floor(lon2 / 360)) - int(floor(lon1 / 360)) 93 | lon1 = Math.IEEEremainder(lon1, 720.0); 94 | lon2 = Math.IEEEremainder(lon2, 720.0); 95 | return ( (lon2 >= 0 && lon2 < 360 ? 0 : 1) - 96 | (lon1 >= 0 && lon1 < 360 ? 0 : 1) ); 97 | } 98 | // reduce Accumulator area to allowed range 99 | private static double AreaReduceA(Accumulator area, double area0, 100 | int crossings, 101 | boolean reverse, boolean sign) { 102 | area.Remainder(area0); 103 | if ((crossings & 1) != 0) 104 | area.Add((area.Sum() < 0 ? 1 : -1) * area0/2); 105 | // area is with the clockwise sense. If !reverse convert to 106 | // counter-clockwise convention. 107 | if (!reverse) 108 | area.Negate(); 109 | // If sign put area in (-area0/2, area0/2], else put area in [0, area0) 110 | if (sign) { 111 | if (area.Sum() > area0/2) 112 | area.Add(-area0); 113 | else if (area.Sum() <= -area0/2) 114 | area.Add(+area0); 115 | } else { 116 | if (area.Sum() >= area0) 117 | area.Add(-area0); 118 | else if (area.Sum() < 0) 119 | area.Add(+area0); 120 | } 121 | return 0 + area.Sum(); 122 | } 123 | // reduce double area to allowed range 124 | private static double AreaReduceB(double area, double area0, 125 | int crossings, 126 | boolean reverse, boolean sign) { 127 | area = Math.IEEEremainder(area, area0); 128 | if ((crossings & 1) != 0) 129 | area += (area < 0 ? 1 : -1) * area0/2; 130 | // area is with the clockwise sense. If !reverse convert to 131 | // counter-clockwise convention. 132 | if (!reverse) 133 | area *= -1; 134 | // If sign put area in (-area0/2, area0/2], else put area in [0, area0) 135 | if (sign) { 136 | if (area > area0/2) 137 | area -= area0; 138 | else if (area <= -area0/2) 139 | area += area0; 140 | } else { 141 | if (area >= area0) 142 | area -= area0; 143 | else if (area < 0) 144 | area += area0; 145 | } 146 | return 0 + area; 147 | } 148 | 149 | /** 150 | * Constructor for PolygonArea. 151 | *

152 | * @param earth the Geodesic object to use for geodesic calculations. 153 | * @param polyline if true that treat the points as defining a polyline 154 | * instead of a polygon. 155 | **********************************************************************/ 156 | public PolygonArea(Geodesic earth, boolean polyline) { 157 | _earth = earth; 158 | _area0 = _earth.EllipsoidArea(); 159 | _polyline = polyline; 160 | _mask = GeodesicMask.LATITUDE | GeodesicMask.LONGITUDE | 161 | GeodesicMask.DISTANCE | 162 | (_polyline ? GeodesicMask.NONE : 163 | GeodesicMask.AREA | GeodesicMask.LONG_UNROLL); 164 | _perimetersum = new Accumulator(0); 165 | if (!_polyline) 166 | _areasum = new Accumulator(0); 167 | Clear(); 168 | } 169 | 170 | /** 171 | * Clear PolygonArea, allowing a new polygon to be started. 172 | **********************************************************************/ 173 | public void Clear() { 174 | _num = 0; 175 | _crossings = 0; 176 | _perimetersum.Set(0); 177 | if (!_polyline) _areasum.Set(0); 178 | _lat0 = _lon0 = _lat1 = _lon1 = Double.NaN; 179 | } 180 | 181 | /** 182 | * Add a point to the polygon or polyline. 183 | *

184 | * @param lat the latitude of the point (degrees). 185 | * @param lon the latitude of the point (degrees). 186 | *

187 | * lat should be in the range [−90°, 90°]. 188 | **********************************************************************/ 189 | public void AddPoint(double lat, double lon) { 190 | if (_num == 0) { 191 | _lat0 = _lat1 = lat; 192 | _lon0 = _lon1 = lon; 193 | } else { 194 | GeodesicData g = _earth.Inverse(_lat1, _lon1, lat, lon, _mask); 195 | _perimetersum.Add(g.s12); 196 | if (!_polyline) { 197 | _areasum.Add(g.S12); 198 | _crossings += transit(_lon1, lon); 199 | } 200 | _lat1 = lat; _lon1 = lon; 201 | } 202 | ++_num; 203 | } 204 | 205 | /** 206 | * Add an edge to the polygon or polyline. 207 | *

208 | * @param azi azimuth at current point (degrees). 209 | * @param s distance from current point to next point (meters). 210 | *

211 | * This does nothing if no points have been added yet. Use 212 | * PolygonArea.CurrentPoint to determine the position of the new vertex. 213 | **********************************************************************/ 214 | public void AddEdge(double azi, double s) { 215 | if (_num > 0) { // Do nothing if _num is zero 216 | GeodesicData g = _earth.Direct(_lat1, _lon1, azi, s, _mask); 217 | _perimetersum.Add(g.s12); 218 | if (!_polyline) { 219 | _areasum.Add(g.S12); 220 | _crossings += transitdirect(_lon1, g.lon2); 221 | } 222 | _lat1 = g.lat2; _lon1 = g.lon2; 223 | ++_num; 224 | } 225 | } 226 | 227 | /** 228 | * Return the results so far. 229 | *

230 | * @return PolygonResult(num, perimeter, area) where 231 | * num is the number of vertices, perimeter is the perimeter 232 | * of the polygon or the length of the polyline (meters), and area 233 | * is the area of the polygon (meters2) or Double.NaN of 234 | * polyline is true in the constructor. 235 | *

236 | * Counter-clockwise traversal counts as a positive area. 237 | **********************************************************************/ 238 | public PolygonResult Compute() { return Compute(false, true); } 239 | /** 240 | * Return the results so far. 241 | *

242 | * @param reverse if true then clockwise (instead of counter-clockwise) 243 | * traversal counts as a positive area. 244 | * @param sign if true then return a signed result for the area if 245 | * the polygon is traversed in the "wrong" direction instead of returning 246 | * the area for the rest of the earth. 247 | * @return PolygonResult(num, perimeter, area) where 248 | * num is the number of vertices, perimeter is the perimeter 249 | * of the polygon or the length of the polyline (meters), and area 250 | * is the area of the polygon (meters2) or Double.NaN of 251 | * polyline is true in the constructor. 252 | *

253 | * More points can be added to the polygon after this call. 254 | **********************************************************************/ 255 | public PolygonResult Compute(boolean reverse, boolean sign) { 256 | if (_num < 2) 257 | return new PolygonResult(_num, 0, _polyline ? Double.NaN : 0); 258 | if (_polyline) 259 | return new PolygonResult(_num, _perimetersum.Sum(), Double.NaN); 260 | 261 | GeodesicData g = _earth.Inverse(_lat1, _lon1, _lat0, _lon0, _mask); 262 | Accumulator tempsum = new Accumulator(_areasum); 263 | tempsum.Add(g.S12); 264 | 265 | return 266 | new PolygonResult(_num, _perimetersum.Sum(g.s12), 267 | AreaReduceA(tempsum, _area0, 268 | _crossings + transit(_lon1, _lon0), 269 | reverse, sign)); 270 | } 271 | 272 | /** 273 | * Return the results assuming a tentative final test point is added; 274 | * however, the data for the test point is not saved. This lets you report 275 | * a running result for the perimeter and area as the user moves the mouse 276 | * cursor. Ordinary floating point arithmetic is used to accumulate the 277 | * data for the test point; thus the area and perimeter returned are less 278 | * accurate than if AddPoint and Compute are used. 279 | *

280 | * @param lat the latitude of the test point (degrees). 281 | * @param lon the longitude of the test point (degrees). 282 | * @param reverse if true then clockwise (instead of counter-clockwise) 283 | * traversal counts as a positive area. 284 | * @param sign if true then return a signed result for the area if 285 | * the polygon is traversed in the "wrong" direction instead of returning 286 | * the area for the rest of the earth. 287 | * @return PolygonResult(num, perimeter, area) where 288 | * num is the number of vertices, perimeter is the perimeter 289 | * of the polygon or the length of the polyline (meters), and area 290 | * is the area of the polygon (meters2) or Double.NaN of 291 | * polyline is true in the constructor. 292 | *

293 | * lat should be in the range [−90°, 90°]. 294 | **********************************************************************/ 295 | public PolygonResult TestPoint(double lat, double lon, 296 | boolean reverse, boolean sign) { 297 | if (_num == 0) 298 | return new PolygonResult(1, 0, _polyline ? Double.NaN : 0); 299 | 300 | double perimeter = _perimetersum.Sum(); 301 | double tempsum = _polyline ? 0 : _areasum.Sum(); 302 | int crossings = _crossings; 303 | int num = _num + 1; 304 | for (int i = 0; i < (_polyline ? 1 : 2); ++i) { 305 | GeodesicData g = 306 | _earth.Inverse(i == 0 ? _lat1 : lat, i == 0 ? _lon1 : lon, 307 | i != 0 ? _lat0 : lat, i != 0 ? _lon0 : lon, 308 | _mask); 309 | perimeter += g.s12; 310 | if (!_polyline) { 311 | tempsum += g.S12; 312 | crossings += transit(i == 0 ? _lon1 : lon, 313 | i != 0 ? _lon0 : lon); 314 | } 315 | } 316 | 317 | if (_polyline) 318 | return new PolygonResult(num, perimeter, Double.NaN); 319 | 320 | return new PolygonResult(num, perimeter, 321 | AreaReduceB(tempsum, _area0, crossings, 322 | reverse, sign)); 323 | } 324 | 325 | /** 326 | * Return the results assuming a tentative final test point is added via an 327 | * azimuth and distance; however, the data for the test point is not saved. 328 | * This lets you report a running result for the perimeter and area as the 329 | * user moves the mouse cursor. Ordinary floating point arithmetic is used 330 | * to accumulate the data for the test point; thus the area and perimeter 331 | * returned are less accurate than if AddPoint and Compute are used. 332 | *

333 | * @param azi azimuth at current point (degrees). 334 | * @param s distance from current point to final test point (meters). 335 | * @param reverse if true then clockwise (instead of counter-clockwise) 336 | * traversal counts as a positive area. 337 | * @param sign if true then return a signed result for the area if 338 | * the polygon is traversed in the "wrong" direction instead of returning 339 | * the area for the rest of the earth. 340 | * @return PolygonResult(num, perimeter, area) where 341 | * num is the number of vertices, perimeter is the perimeter 342 | * of the polygon or the length of the polyline (meters), and area 343 | * is the area of the polygon (meters2) or Double.NaN of 344 | * polyline is true in the constructor. 345 | **********************************************************************/ 346 | public PolygonResult TestEdge(double azi, double s, 347 | boolean reverse, boolean sign) { 348 | if (_num == 0) // we don't have a starting point! 349 | return new PolygonResult(0, Double.NaN, Double.NaN); 350 | 351 | int num = _num + 1; 352 | double perimeter = _perimetersum.Sum() + s; 353 | if (_polyline) 354 | return new PolygonResult(num, perimeter, Double.NaN); 355 | 356 | double tempsum = _areasum.Sum(); 357 | int crossings = _crossings; 358 | { 359 | double lat, lon, s12, S12, t; 360 | GeodesicData g = 361 | _earth.Direct(_lat1, _lon1, azi, false, s, _mask); 362 | tempsum += g.S12; 363 | crossings += transitdirect(_lon1, g.lon2); 364 | crossings += transit(g.lon2, _lon0); 365 | g = _earth.Inverse(g.lat2, g.lon2, _lat0, _lon0, _mask); 366 | perimeter += g.s12; 367 | tempsum += g.S12; 368 | } 369 | 370 | return new PolygonResult(num, perimeter, 371 | AreaReduceB(tempsum, _area0, crossings, 372 | reverse, sign)); 373 | } 374 | 375 | /** 376 | * @return a the equatorial radius of the ellipsoid (meters). This is 377 | * the value inherited from the Geodesic object used in the constructor. 378 | **********************************************************************/ 379 | public double EquatorialRadius() { return _earth.EquatorialRadius(); } 380 | 381 | /** 382 | * @return f the flattening of the ellipsoid. This is the value 383 | * inherited from the Geodesic object used in the constructor. 384 | **********************************************************************/ 385 | public double Flattening() { return _earth.Flattening(); } 386 | 387 | /** 388 | * Report the previous vertex added to the polygon or polyline. 389 | *

390 | * @return Pair(lat, lon), the current latitude and longitude. 391 | *

392 | * If no points have been added, then Double.NaN is returned. Otherwise, 393 | * lon will be in the range [−180°, 180°]. 394 | **********************************************************************/ 395 | public Pair CurrentPoint() { return new Pair(_lat1, _lon1); } 396 | } 397 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/PolygonResult.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the net.sf.geographiclib.PolygonResult class 3 | * 4 | * Copyright (c) Charles Karney (2013) and licensed 5 | * under the MIT/X11 License. For more information, see 6 | * https://geographiclib.sourceforge.io/ 7 | **********************************************************************/ 8 | package net.sf.geographiclib; 9 | 10 | /** 11 | * A container for the results from PolygonArea. 12 | **********************************************************************/ 13 | public class PolygonResult { 14 | /** 15 | * The number of vertices in the polygon 16 | **********************************************************************/ 17 | public int num; 18 | /** 19 | * The perimeter of the polygon or the length of the polyline (meters). 20 | **********************************************************************/ 21 | public double perimeter; 22 | /** 23 | * The area of the polygon (meters2). 24 | **********************************************************************/ 25 | public double area; 26 | /** 27 | * Constructor 28 | *

29 | * @param num the number of vertices in the polygon. 30 | * @param perimeter the perimeter of the polygon or the length of the 31 | * polyline (meters). 32 | * @param area the area of the polygon (meters2). 33 | **********************************************************************/ 34 | public PolygonResult(int num, double perimeter, double area) { 35 | this.num = num; 36 | this.perimeter = perimeter; 37 | this.area = area; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/sf/geographiclib/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | *

Geodesic routines from GeographicLib implemented in Java

3 | * @author Charles F. F. Karney (charles@karney.com) 4 | * @version 2.0 5 | * 6 | *

7 | * The documentation for other versions is available at 8 | * https://geographiclib.sourceforge.io/Java/. 9 | *

10 | * Licensed under the 11 | * MIT/X11 License; see 12 | * 13 | * LICENSE.txt. 14 | * 15 | *

Abstract

16 | *

17 | * GeographicLib-Java is a Java implementation of the geodesic algorithms from 18 | * GeographicLib. This is a 19 | * self-contained library which makes it easy to do geodesic computations for 20 | * an ellipsoid of revolution in a Java program. It requires Java version 1.8 21 | * or later. 22 | * 23 | *

Downloading

24 | *

25 | * Download either the source or the pre-built package as follows: 26 | * 27 | *

The pre-built package

28 | * GeographicLib-Java is available as a 29 | * 30 | * pre-built package on Maven Central (thanks to Chris Bennight for help on 31 | * this deployment). So, if you use 32 | * maven to build your code, you just 33 | * need to include the dependency
{@code
 34 |  *   
 35 |  *     net.sf.geographiclib
 36 |  *     GeographicLib-Java
 37 |  *     2.0
 38 |  *    }
39 | * in your {@code pom.xml}. 40 | * 41 | *

Obtaining the source

42 | * The source is hosted on 43 | * github. 44 | * Releases are tagged as v1.52, v2.0, etc. 45 | * 46 | *

Sample programs

47 | *

48 | * Included with the source are 3 small test programs 49 | *

60 | *

61 | * Here, for example, is {@code Inverse.java}

{@code
 62 |  * // Solve the inverse geodesic problem.
 63 |  * //
 64 |  * // This program reads in lines with lat1, lon1, lat2, lon2 and prints
 65 |  * // out lines with azi1, azi2, s12 (for the WGS84 ellipsoid).
 66 |  *
 67 |  * import java.util.*;
 68 |  * import net.sf.geographiclib.*;
 69 |  * public class Inverse {
 70 |  *   public static void main(String[] args) {
 71 |  *     try {
 72 |  *       Scanner in = new Scanner(System.in);
 73 |  *       double lat1, lon1, lat2, lon2;
 74 |  *       while (true) {
 75 |  *         lat1 = in.nextDouble(); lon1 = in.nextDouble();
 76 |  *         lat2 = in.nextDouble(); lon2 = in.nextDouble();
 77 |  *         GeodesicData g = Geodesic.WGS84.Inverse(lat1, lon1, lat2, lon2);
 78 |  *         System.out.format("%.11f %.11f %.6f%n", g.azi1, g.azi2, g.s12);
 79 |  *       }
 80 |  *     }
 81 |  *     catch (Exception e) {}
 82 |  *   }
 83 |  * }}
84 | * 85 | *

Compiling and running a sample program

86 | *

87 | * Here's how to compile and run {@code Inverse.java} using 88 | * maven (the recommended way) and 89 | * without using maven. (Thanks to Skip Breidbach for supplying the maven 90 | * support.) 91 | * 92 | *

Using maven

93 | * The sample code includes a {@code pom.xml} which specifies 94 | * GeographicLib-Java as a dependency which maven will download from Maven 95 | * Central. You can compile and run Inverse.java with
 96 |  * cd inverse
 97 |  * mvn compile
 98 |  * echo -30 0 29.5 179.5 | mvn -q exec:java 
99 | * 100 | *

Without using maven

101 | * Compile and run as follows
102 |  * cd inverse/src/main/java
103 |  * javac -cp .:../../../../src/main/java Inverse.java
104 |  * echo -30 0 29.5 179.5 | java -cp .:../../../../src/main/java Inverse 
105 | * 106 | *

Using the library

107 | * 117 | *

118 | * The important classes are 119 | *

142 | *

143 | * The documentation is generated using javadoc when 144 | * {@code mvn package -P release} is run (the top of the documentation tree is 145 | * {@code target/apidocs/index.html}). This is also available on the web at 146 | * 147 | * https://geographiclib.sourceforge.io/Java/doc/index.html. 148 | * 149 | *

External links

150 | * 182 | * 183 | *

Change log

184 | * 355 | **********************************************************************/ 356 | package net.sf.geographiclib; 357 | -------------------------------------------------------------------------------- /src/test/java/net/sf/geographiclib/GeodesicTest.java: -------------------------------------------------------------------------------- 1 | package net.sf.geographiclib; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import org.junit.Test; 6 | 7 | public class GeodesicTest { 8 | 9 | private static final PolygonArea polygon = 10 | new PolygonArea(Geodesic.WGS84, false); 11 | private static final PolygonArea polyline = 12 | new PolygonArea(Geodesic.WGS84, true); 13 | 14 | private static PolygonResult Planimeter(double points[][]) { 15 | polygon.Clear(); 16 | for (int i = 0; i < points.length; ++i) { 17 | polygon.AddPoint(points[i][0], points[i][1]); 18 | } 19 | return polygon.Compute(false, true); 20 | } 21 | 22 | private static PolygonResult PolyLength(double points[][]) { 23 | polyline.Clear(); 24 | for (int i = 0; i < points.length; ++i) { 25 | polyline.AddPoint(points[i][0], points[i][1]); 26 | } 27 | return polyline.Compute(false, true); 28 | } 29 | 30 | private static final double testcases[][] = { 31 | {35.60777, -139.44815, 111.098748429560326, 32 | -11.17491, -69.95921, 129.289270889708762, 33 | 8935244.5604818305, 80.50729714281974, 6273170.2055303837, 34 | 0.16606318447386067, 0.16479116945612937, 12841384694976.432}, 35 | {55.52454, 106.05087, 22.020059880982801, 36 | 77.03196, 197.18234, 109.112041110671519, 37 | 4105086.1713924406, 36.892740690445894, 3828869.3344387607, 38 | 0.80076349608092607, 0.80101006984201008, 61674961290615.615}, 39 | {-21.97856, 142.59065, -32.44456876433189, 40 | 41.84138, 98.56635, -41.84359951440466, 41 | 8394328.894657671, 75.62930491011522, 6161154.5773110616, 42 | 0.24816339233950381, 0.24930251203627892, -6637997720646.717}, 43 | {-66.99028, 112.2363, 173.73491240878403, 44 | -12.70631, 285.90344, 2.512956620913668, 45 | 11150344.2312080241, 100.278634181155759, 6289939.5670446687, 46 | -0.17199490274700385, -0.17722569526345708, -121287239862139.744}, 47 | {-17.42761, 173.34268, -159.033557661192928, 48 | -15.84784, 5.93557, -20.787484651536988, 49 | 16076603.1631180673, 144.640108810286253, 3732902.1583877189, 50 | -0.81273638700070476, -0.81299800519154474, 97825992354058.708}, 51 | {32.84994, 48.28919, 150.492927788121982, 52 | -56.28556, 202.29132, 48.113449399816759, 53 | 16727068.9438164461, 150.565799985466607, 3147838.1910180939, 54 | -0.87334918086923126, -0.86505036767110637, -72445258525585.010}, 55 | {6.96833, 52.74123, 92.581585386317712, 56 | -7.39675, 206.17291, 90.721692165923907, 57 | 17102477.2496958388, 154.147366239113561, 2772035.6169917581, 58 | -0.89991282520302447, -0.89986892177110739, -1311796973197.995}, 59 | {-50.56724, -16.30485, -105.439679907590164, 60 | -33.56571, -94.97412, -47.348547835650331, 61 | 6455670.5118668696, 58.083719495371259, 5409150.7979815838, 62 | 0.53053508035997263, 0.52988722644436602, 41071447902810.047}, 63 | {-58.93002, -8.90775, 140.965397902500679, 64 | -8.91104, 133.13503, 19.255429433416599, 65 | 11756066.0219864627, 105.755691241406877, 6151101.2270708536, 66 | -0.26548622269867183, -0.27068483874510741, -86143460552774.735}, 67 | {-68.82867, -74.28391, 93.774347763114881, 68 | -50.63005, -8.36685, 34.65564085411343, 69 | 3956936.926063544, 35.572254987389284, 3708890.9544062657, 70 | 0.81443963736383502, 0.81420859815358342, -41845309450093.787}, 71 | {-10.62672, -32.0898, -86.426713286747751, 72 | 5.883, -134.31681, -80.473780971034875, 73 | 11470869.3864563009, 103.387395634504061, 6184411.6622659713, 74 | -0.23138683500430237, -0.23155097622286792, 4198803992123.548}, 75 | {-21.76221, 166.90563, 29.319421206936428, 76 | 48.72884, 213.97627, 43.508671946410168, 77 | 9098627.3986554915, 81.963476716121964, 6299240.9166992283, 78 | 0.13965943368590333, 0.14152969707656796, 10024709850277.476}, 79 | {-19.79938, -174.47484, 71.167275780171533, 80 | -11.99349, -154.35109, 65.589099775199228, 81 | 2319004.8601169389, 20.896611684802389, 2267960.8703918325, 82 | 0.93427001867125849, 0.93424887135032789, -3935477535005.785}, 83 | {-11.95887, -116.94513, 92.712619830452549, 84 | 4.57352, 7.16501, 78.64960934409585, 85 | 13834722.5801401374, 124.688684161089762, 5228093.177931598, 86 | -0.56879356755666463, -0.56918731952397221, -9919582785894.853}, 87 | {-87.85331, 85.66836, -65.120313040242748, 88 | 66.48646, 16.09921, -4.888658719272296, 89 | 17286615.3147144645, 155.58592449699137, 2635887.4729110181, 90 | -0.90697975771398578, -0.91095608883042767, 42667211366919.534}, 91 | {1.74708, 128.32011, -101.584843631173858, 92 | -11.16617, 11.87109, -86.325793296437476, 93 | 12942901.1241347408, 116.650512484301857, 5682744.8413270572, 94 | -0.44857868222697644, -0.44824490340007729, 10763055294345.653}, 95 | {-25.72959, -144.90758, -153.647468693117198, 96 | -57.70581, -269.17879, -48.343983158876487, 97 | 9413446.7452453107, 84.664533838404295, 6356176.6898881281, 98 | 0.09492245755254703, 0.09737058264766572, 74515122850712.444}, 99 | {-41.22777, 122.32875, 14.285113402275739, 100 | -7.57291, 130.37946, 10.805303085187369, 101 | 3812686.035106021, 34.34330804743883, 3588703.8812128856, 102 | 0.82605222593217889, 0.82572158200920196, -2456961531057.857}, 103 | {11.01307, 138.25278, 79.43682622782374, 104 | 6.62726, 247.05981, 103.708090215522657, 105 | 11911190.819018408, 107.341669954114577, 6070904.722786735, 106 | -0.29767608923657404, -0.29785143390252321, 17121631423099.696}, 107 | {-29.47124, 95.14681, -163.779130441688382, 108 | -27.46601, -69.15955, -15.909335945554969, 109 | 13487015.8381145492, 121.294026715742277, 5481428.9945736388, 110 | -0.51527225545373252, -0.51556587964721788, 104679964020340.318}}; 111 | 112 | @Test 113 | public void InverseCheck() { 114 | for (int i = 0; i < testcases.length; ++i) { 115 | double 116 | lat1 = testcases[i][0], lon1 = testcases[i][1], azi1 = testcases[i][2], 117 | lat2 = testcases[i][3], lon2 = testcases[i][4], azi2 = testcases[i][5], 118 | s12 = testcases[i][6], a12 = testcases[i][7], m12 = testcases[i][8], 119 | M12 = testcases[i][9], M21 = testcases[i][10], S12 = testcases[i][11]; 120 | GeodesicData inv = Geodesic.WGS84.Inverse(lat1, lon1, lat2, lon2, 121 | GeodesicMask.ALL | 122 | GeodesicMask.LONG_UNROLL); 123 | assertEquals(lon2, inv.lon2, 1e-13); 124 | assertEquals(azi1, inv.azi1, 1e-13); 125 | assertEquals(azi2, inv.azi2, 1e-13); 126 | assertEquals(s12, inv.s12, 1e-8); 127 | assertEquals(a12, inv.a12, 1e-13); 128 | assertEquals(m12, inv.m12, 1e-8); 129 | assertEquals(M12, inv.M12, 1e-15); 130 | assertEquals(M21, inv.M21, 1e-15); 131 | assertEquals(S12, inv.S12, 0.1); 132 | } 133 | } 134 | 135 | @Test 136 | public void DirectCheck() { 137 | for (int i = 0; i < testcases.length; ++i) { 138 | double 139 | lat1 = testcases[i][0], lon1 = testcases[i][1], azi1 = testcases[i][2], 140 | lat2 = testcases[i][3], lon2 = testcases[i][4], azi2 = testcases[i][5], 141 | s12 = testcases[i][6], a12 = testcases[i][7], m12 = testcases[i][8], 142 | M12 = testcases[i][9], M21 = testcases[i][10], S12 = testcases[i][11]; 143 | GeodesicData dir = Geodesic.WGS84.Direct(lat1, lon1, azi1, s12, 144 | GeodesicMask.ALL | 145 | GeodesicMask.LONG_UNROLL); 146 | assertEquals(lat2, dir.lat2, 1e-13); 147 | assertEquals(lon2, dir.lon2, 1e-13); 148 | assertEquals(azi2, dir.azi2, 1e-13); 149 | assertEquals(a12, dir.a12, 1e-13); 150 | assertEquals(m12, dir.m12, 1e-8); 151 | assertEquals(M12, dir.M12, 1e-15); 152 | assertEquals(M21, dir.M21, 1e-15); 153 | assertEquals(S12, dir.S12, 0.1); 154 | } 155 | } 156 | 157 | @Test 158 | public void ArcDirectCheck() { 159 | for (int i = 0; i < testcases.length; ++i) { 160 | double 161 | lat1 = testcases[i][0], lon1 = testcases[i][1], azi1 = testcases[i][2], 162 | lat2 = testcases[i][3], lon2 = testcases[i][4], azi2 = testcases[i][5], 163 | s12 = testcases[i][6], a12 = testcases[i][7], m12 = testcases[i][8], 164 | M12 = testcases[i][9], M21 = testcases[i][10], S12 = testcases[i][11]; 165 | GeodesicData dir = Geodesic.WGS84.ArcDirect(lat1, lon1, azi1, a12, 166 | GeodesicMask.ALL | 167 | GeodesicMask.LONG_UNROLL); 168 | assertEquals(lat2, dir.lat2, 1e-13); 169 | assertEquals(lon2, dir.lon2, 1e-13); 170 | assertEquals(azi2, dir.azi2, 1e-13); 171 | assertEquals(s12, dir.s12, 1e-8); 172 | assertEquals(m12, dir.m12, 1e-8); 173 | assertEquals(M12, dir.M12, 1e-15); 174 | assertEquals(M21, dir.M21, 1e-15); 175 | assertEquals(S12, dir.S12, 0.1); 176 | } 177 | } 178 | 179 | @Test 180 | public void GeodSolve0() { 181 | GeodesicData inv = Geodesic.WGS84.Inverse(40.6, -73.8, 182 | 49.01666667, 2.55); 183 | assertEquals(inv.azi1, 53.47022, 0.5e-5); 184 | assertEquals(inv.azi2, 111.59367, 0.5e-5); 185 | assertEquals(inv.s12, 5853226, 0.5); 186 | } 187 | 188 | @Test 189 | public void GeodSolve1() { 190 | GeodesicData dir = Geodesic.WGS84.Direct(40.63972222, -73.77888889, 191 | 53.5, 5850e3); 192 | assertEquals(dir.lat2, 49.01467, 0.5e-5); 193 | assertEquals(dir.lon2, 2.56106, 0.5e-5); 194 | assertEquals(dir.azi2, 111.62947, 0.5e-5); 195 | } 196 | 197 | @Test 198 | public void GeodSolve2() { 199 | // Check fix for antipodal prolate bug found 2010-09-04 200 | Geodesic geod = new Geodesic(6.4e6, -1/150.0); 201 | GeodesicData inv = geod.Inverse(0.07476, 0, -0.07476, 180); 202 | assertEquals(inv.azi1, 90.00078, 0.5e-5); 203 | assertEquals(inv.azi2, 90.00078, 0.5e-5); 204 | assertEquals(inv.s12, 20106193, 0.5); 205 | inv = geod.Inverse(0.1, 0, -0.1, 180); 206 | assertEquals(inv.azi1, 90.00105, 0.5e-5); 207 | assertEquals(inv.azi2, 90.00105, 0.5e-5); 208 | assertEquals(inv.s12, 20106193, 0.5); 209 | } 210 | 211 | @Test 212 | public void GeodSolve4() { 213 | // Check fix for short line bug found 2010-05-21 214 | GeodesicData inv = Geodesic.WGS84.Inverse(36.493349428792, 0, 215 | 36.49334942879201, .0000008); 216 | assertEquals(inv.s12, 0.072, 0.5e-3); 217 | } 218 | 219 | @Test 220 | public void GeodSolve5() { 221 | // Check fix for point2=pole bug found 2010-05-03 222 | GeodesicData dir = Geodesic.WGS84.Direct(0.01777745589997, 30, 0, 10e6); 223 | assertEquals(dir.lat2, 90, 0.5e-5); 224 | if (dir.lon2 < 0) { 225 | assertEquals(dir.lon2, -150, 0.5e-5); 226 | assertEquals(Math.abs(dir.azi2), 180, 0.5e-5); 227 | } else { 228 | assertEquals(dir.lon2, 30, 0.5e-5); 229 | assertEquals(dir.azi2, 0, 0.5e-5); 230 | } 231 | } 232 | 233 | @Test 234 | public void GeodSolve6() { 235 | // Check fix for volatile sbet12a bug found 2011-06-25 (gcc 4.4.4 236 | // x86 -O3). Found again on 2012-03-27 with tdm-mingw32 (g++ 4.6.1). 237 | GeodesicData inv = 238 | Geodesic.WGS84.Inverse(88.202499451857, 0, 239 | -88.202499451857, 179.981022032992859592); 240 | assertEquals(inv.s12, 20003898.214, 0.5e-3); 241 | inv = Geodesic.WGS84.Inverse(89.262080389218, 0, 242 | -89.262080389218, 179.992207982775375662); 243 | assertEquals(inv.s12, 20003925.854, 0.5e-3); 244 | inv = Geodesic.WGS84.Inverse(89.333123580033, 0, -89.333123580032997687, 245 | 179.99295812360148422); 246 | assertEquals(inv.s12, 20003926.881, 0.5e-3); 247 | } 248 | 249 | @Test 250 | public void GeodSolve9() { 251 | // Check fix for volatile x bug found 2011-06-25 (gcc 4.4.4 x86 -O3) 252 | GeodesicData inv = 253 | Geodesic.WGS84.Inverse(56.320923501171, 0, 254 | -56.320923501171, 179.664747671772880215); 255 | assertEquals(inv.s12, 19993558.287, 0.5e-3); 256 | } 257 | 258 | @Test 259 | public void GeodSolve10() { 260 | // Check fix for adjust tol1_ bug found 2011-06-25 (Visual Studio 261 | // 10 rel + debug) 262 | GeodesicData inv = 263 | Geodesic.WGS84.Inverse(52.784459512564, 0, 264 | -52.784459512563990912, 179.634407464943777557); 265 | assertEquals(inv.s12, 19991596.095, 0.5e-3); 266 | } 267 | 268 | @Test 269 | public void GeodSolve11() { 270 | // Check fix for bet2 = -bet1 bug found 2011-06-25 (Visual Studio 271 | // 10 rel + debug) 272 | GeodesicData inv = 273 | Geodesic.WGS84.Inverse(48.522876735459, 0, 274 | -48.52287673545898293, 179.599720456223079643); 275 | assertEquals(inv.s12, 19989144.774, 0.5e-3); 276 | } 277 | 278 | @Test 279 | public void GeodSolve12() { 280 | // Check fix for inverse geodesics on extreme prolate/oblate 281 | // ellipsoids Reported 2012-08-29 Stefan Guenther 282 | // ; fixed 2012-10-07 283 | Geodesic geod = new Geodesic(89.8, -1.83); 284 | GeodesicData inv = geod.Inverse(0, 0, -10, 160); 285 | assertEquals(inv.azi1, 120.27, 1e-2); 286 | assertEquals(inv.azi2, 105.15, 1e-2); 287 | assertEquals(inv.s12, 266.7, 1e-1); 288 | } 289 | 290 | @Test 291 | public void GeodSolve14() { 292 | // Check fix for inverse ignoring lon12 = nan 293 | GeodesicData inv = Geodesic.WGS84.Inverse(0, 0, 1, Double.NaN); 294 | assertTrue(Double.isNaN(inv.azi1)); 295 | assertTrue(Double.isNaN(inv.azi2)); 296 | assertTrue(Double.isNaN(inv.s12)); 297 | } 298 | 299 | @Test 300 | public void GeodSolve15() { 301 | // Initial implementation of Math::eatanhe was wrong for e^2 < 0. This 302 | // checks that this is fixed. 303 | Geodesic geod = new Geodesic(6.4e6, -1/150.0); 304 | GeodesicData dir = geod.Direct(1, 2, 3, 4, GeodesicMask.AREA); 305 | assertEquals(dir.S12, 23700, 0.5); 306 | } 307 | 308 | @Test 309 | public void GeodSolve17() { 310 | // Check fix for LONG_UNROLL bug found on 2015-05-07 311 | GeodesicData dir = 312 | Geodesic.WGS84.Direct(40, -75, -10, 2e7, 313 | GeodesicMask.STANDARD | GeodesicMask.LONG_UNROLL); 314 | assertEquals(dir.lat2, -39, 1); 315 | assertEquals(dir.lon2, -254, 1); 316 | assertEquals(dir.azi2, -170, 1); 317 | GeodesicLine line = Geodesic.WGS84.Line(40, -75, -10); 318 | dir = line.Position(2e7, GeodesicMask.STANDARD | GeodesicMask.LONG_UNROLL); 319 | assertEquals(dir.lat2, -39, 1); 320 | assertEquals(dir.lon2, -254, 1); 321 | assertEquals(dir.azi2, -170, 1); 322 | dir = Geodesic.WGS84.Direct(40, -75, -10, 2e7); 323 | assertEquals(dir.lat2, -39, 1); 324 | assertEquals(dir.lon2, 105, 1); 325 | assertEquals(dir.azi2, -170, 1); 326 | dir = line.Position(2e7); 327 | assertEquals(dir.lat2, -39, 1); 328 | assertEquals(dir.lon2, 105, 1); 329 | assertEquals(dir.azi2, -170, 1); 330 | } 331 | 332 | @Test 333 | public void GeodSolve26() { 334 | // Check 0/0 problem with area calculation on sphere 2015-09-08 335 | Geodesic geod = new Geodesic(6.4e6, 0); 336 | GeodesicData inv = geod.Inverse(1, 2, 3, 4, GeodesicMask.AREA); 337 | assertEquals(inv.S12, 49911046115.0, 0.5); 338 | } 339 | 340 | @Test 341 | public void GeodSolve28() { 342 | // Check for bad placement of assignment of r.a12 with |f| > 0.01 (bug in 343 | // Java implementation fixed on 2015-05-19). 344 | Geodesic geod = new Geodesic(6.4e6, 0.1); 345 | GeodesicData dir = geod.Direct(1, 2, 10, 5e6); 346 | assertEquals(dir.a12, 48.55570690, 0.5e-8); 347 | } 348 | 349 | @Test 350 | public void GeodSolve29() { 351 | // Check longitude unrolling with inverse calculation 2015-09-16 352 | GeodesicData dir = Geodesic.WGS84.Inverse(0, 539, 0, 181); 353 | assertEquals(dir.lon1, 179, 1e-10); 354 | assertEquals(dir.lon2, -179, 1e-10); 355 | assertEquals(dir.s12, 222639, 0.5); 356 | dir = Geodesic.WGS84.Inverse(0, 539, 0, 181, 357 | GeodesicMask.STANDARD | 358 | GeodesicMask.LONG_UNROLL); 359 | assertEquals(dir.lon1, 539, 1e-10); 360 | assertEquals(dir.lon2, 541, 1e-10); 361 | assertEquals(dir.s12, 222639, 0.5); 362 | } 363 | 364 | @Test 365 | public void GeodSolve33() { 366 | // Check max(-0.0,+0.0) issues 2015-08-22 (triggered by bugs in Octave -- 367 | // sind(-0.0) = +0.0 -- and in some version of Visual Studio -- 368 | // fmod(-0.0, 360.0) = +0.0. 369 | GeodesicData inv = Geodesic.WGS84.Inverse(0, 0, 0, 179); 370 | assertEquals(inv.azi1, 90.00000, 0.5e-5); 371 | assertEquals(inv.azi2, 90.00000, 0.5e-5); 372 | assertEquals(inv.s12, 19926189, 0.5); 373 | inv = Geodesic.WGS84.Inverse(0, 0, 0, 179.5); 374 | assertEquals(inv.azi1, 55.96650, 0.5e-5); 375 | assertEquals(inv.azi2, 124.03350, 0.5e-5); 376 | assertEquals(inv.s12, 19980862, 0.5); 377 | inv = Geodesic.WGS84.Inverse(0, 0, 0, 180); 378 | assertEquals(inv.azi1, 0.00000, 0.5e-5); 379 | assertEquals(Math.abs(inv.azi2), 180.00000, 0.5e-5); 380 | assertEquals(inv.s12, 20003931, 0.5); 381 | inv = Geodesic.WGS84.Inverse(0, 0, 1, 180); 382 | assertEquals(inv.azi1, 0.00000, 0.5e-5); 383 | assertEquals(Math.abs(inv.azi2), 180.00000, 0.5e-5); 384 | assertEquals(inv.s12, 19893357, 0.5); 385 | Geodesic geod = new Geodesic(6.4e6, 0); 386 | inv = geod.Inverse(0, 0, 0, 179); 387 | assertEquals(inv.azi1, 90.00000, 0.5e-5); 388 | assertEquals(inv.azi2, 90.00000, 0.5e-5); 389 | assertEquals(inv.s12, 19994492, 0.5); 390 | inv = geod.Inverse(0, 0, 0, 180); 391 | assertEquals(inv.azi1, 0.00000, 0.5e-5); 392 | assertEquals(Math.abs(inv.azi2), 180.00000, 0.5e-5); 393 | assertEquals(inv.s12, 20106193, 0.5); 394 | inv = geod.Inverse(0, 0, 1, 180); 395 | assertEquals(inv.azi1, 0.00000, 0.5e-5); 396 | assertEquals(Math.abs(inv.azi2), 180.00000, 0.5e-5); 397 | assertEquals(inv.s12, 19994492, 0.5); 398 | geod = new Geodesic(6.4e6, -1/300.0); 399 | inv = geod.Inverse(0, 0, 0, 179); 400 | assertEquals(inv.azi1, 90.00000, 0.5e-5); 401 | assertEquals(inv.azi2, 90.00000, 0.5e-5); 402 | assertEquals(inv.s12, 19994492, 0.5); 403 | inv = geod.Inverse(0, 0, 0, 180); 404 | assertEquals(inv.azi1, 90.00000, 0.5e-5); 405 | assertEquals(inv.azi2, 90.00000, 0.5e-5); 406 | assertEquals(inv.s12, 20106193, 0.5); 407 | inv = geod.Inverse(0, 0, 0.5, 180); 408 | assertEquals(inv.azi1, 33.02493, 0.5e-5); 409 | assertEquals(inv.azi2, 146.97364, 0.5e-5); 410 | assertEquals(inv.s12, 20082617, 0.5); 411 | inv = geod.Inverse(0, 0, 1, 180); 412 | assertEquals(inv.azi1, 0.00000, 0.5e-5); 413 | assertEquals(Math.abs(inv.azi2), 180.00000, 0.5e-5); 414 | assertEquals(inv.s12, 20027270, 0.5); 415 | } 416 | 417 | @Test 418 | public void GeodSolve55() { 419 | // Check fix for nan + point on equator or pole not returning all nans in 420 | // Geodesic::Inverse, found 2015-09-23. 421 | GeodesicData inv = Geodesic.WGS84.Inverse(Double.NaN, 0, 0, 90); 422 | assertTrue(Double.isNaN(inv.azi1)); 423 | assertTrue(Double.isNaN(inv.azi2)); 424 | assertTrue(Double.isNaN(inv.s12)); 425 | inv = Geodesic.WGS84.Inverse(Double.NaN, 0, 90, 3); 426 | assertTrue(Double.isNaN(inv.azi1)); 427 | assertTrue(Double.isNaN(inv.azi2)); 428 | assertTrue(Double.isNaN(inv.s12)); 429 | } 430 | 431 | @Test 432 | public void GeodSolve59() { 433 | // Check for points close with longitudes close to 180 deg apart. 434 | GeodesicData inv = Geodesic.WGS84.Inverse(5, 0.00000000000001, 10, 180); 435 | assertEquals(inv.azi1, 0.000000000000035, 1.5e-14); 436 | assertEquals(inv.azi2, 179.99999999999996, 1.5e-14); 437 | assertEquals(inv.s12, 18345191.174332713, 5e-9); 438 | } 439 | 440 | @Test 441 | public void GeodSolve61() { 442 | // Make sure small negative azimuths are west-going 443 | GeodesicData dir = 444 | Geodesic.WGS84.Direct(45, 0, -0.000000000000000003, 1e7, 445 | GeodesicMask.STANDARD | GeodesicMask.LONG_UNROLL); 446 | assertEquals(dir.lat2, 45.30632, 0.5e-5); 447 | assertEquals(dir.lon2, -180, 0.5e-5); 448 | assertEquals(Math.abs(dir.azi2), 180, 0.5e-5); 449 | GeodesicLine line = Geodesic.WGS84.InverseLine(45, 0, 80, 450 | -0.000000000000000003); 451 | dir = line.Position(1e7, GeodesicMask.STANDARD | GeodesicMask.LONG_UNROLL); 452 | assertEquals(dir.lat2, 45.30632, 0.5e-5); 453 | assertEquals(dir.lon2, -180, 0.5e-5); 454 | assertEquals(Math.abs(dir.azi2), 180, 0.5e-5); 455 | } 456 | 457 | @Test 458 | public void GeodSolve65() { 459 | // Check for bug in east-going check in GeodesicLine (needed to check for 460 | // sign of 0) and sign error in area calculation due to a bogus override 461 | // of the code for alp12. Found/fixed on 2015-12-19. 462 | GeodesicLine line = Geodesic.WGS84.InverseLine(30, -0.000000000000000001, 463 | -31, 180); 464 | GeodesicData dir = 465 | line.Position(1e7, GeodesicMask.ALL | GeodesicMask.LONG_UNROLL); 466 | assertEquals(dir.lat1, 30.00000 , 0.5e-5); 467 | assertEquals(dir.lon1, -0.00000 , 0.5e-5); 468 | assertEquals(Math.abs(dir.azi1), 180.00000, 0.5e-5); 469 | assertEquals(dir.lat2, -60.23169 , 0.5e-5); 470 | assertEquals(dir.lon2, -0.00000 , 0.5e-5); 471 | assertEquals(Math.abs(dir.azi2), 180.00000, 0.5e-5); 472 | assertEquals(dir.s12 , 10000000 , 0.5); 473 | assertEquals(dir.a12 , 90.06544 , 0.5e-5); 474 | assertEquals(dir.m12 , 6363636 , 0.5); 475 | assertEquals(dir.M12 , -0.0012834, 0.5e7); 476 | assertEquals(dir.M21 , 0.0013749 , 0.5e-7); 477 | assertEquals(dir.S12 , 0 , 0.5); 478 | dir = line.Position(2e7, GeodesicMask.ALL | GeodesicMask.LONG_UNROLL); 479 | assertEquals(dir.lat1, 30.00000 , 0.5e-5); 480 | assertEquals(dir.lon1, -0.00000 , 0.5e-5); 481 | assertEquals(Math.abs(dir.azi1), 180.00000, 0.5e-5); 482 | assertEquals(dir.lat2, -30.03547 , 0.5e-5); 483 | assertEquals(dir.lon2, -180.00000, 0.5e-5); 484 | assertEquals(dir.azi2, -0.00000 , 0.5e-5); 485 | assertEquals(dir.s12 , 20000000 , 0.5); 486 | assertEquals(dir.a12 , 179.96459 , 0.5e-5); 487 | assertEquals(dir.m12 , 54342 , 0.5); 488 | assertEquals(dir.M12 , -1.0045592, 0.5e7); 489 | assertEquals(dir.M21 , -0.9954339, 0.5e-7); 490 | assertEquals(dir.S12 , 127516405431022.0, 0.5); 491 | } 492 | 493 | @Test 494 | public void GeodSolve69() { 495 | // Check for InverseLine if line is slightly west of S and that s13 is 496 | // correctly set. 497 | GeodesicLine line = 498 | Geodesic.WGS84.InverseLine(-5, -0.000000000000002, -10, 180); 499 | GeodesicData dir = 500 | line.Position(2e7, GeodesicMask.STANDARD | GeodesicMask.LONG_UNROLL); 501 | assertEquals(dir.lat2, 4.96445 , 0.5e-5); 502 | assertEquals(dir.lon2, -180.00000, 0.5e-5); 503 | assertEquals(dir.azi2, -0.00000 , 0.5e-5); 504 | dir = line.Position(0.5 * line.Distance(), 505 | GeodesicMask.STANDARD | GeodesicMask.LONG_UNROLL); 506 | assertEquals(dir.lat2, -87.52461 , 0.5e-5); 507 | assertEquals(dir.lon2, -0.00000 , 0.5e-5); 508 | assertEquals(dir.azi2, -180.00000, 0.5e-5); 509 | } 510 | 511 | @Test 512 | public void GeodSolve71() { 513 | // Check that DirectLine sets s13. 514 | GeodesicLine line = Geodesic.WGS84.DirectLine(1, 2, 45, 1e7); 515 | GeodesicData dir = 516 | line.Position(0.5 * line.Distance(), 517 | GeodesicMask.STANDARD | GeodesicMask.LONG_UNROLL); 518 | assertEquals(dir.lat2, 30.92625, 0.5e-5); 519 | assertEquals(dir.lon2, 37.54640, 0.5e-5); 520 | assertEquals(dir.azi2, 55.43104, 0.5e-5); 521 | } 522 | 523 | @Test 524 | public void GeodSolve73() { 525 | // Check for backwards from the pole bug reported by Anon on 2016-02-13. 526 | // This only affected the Java implementation. It was introduced in Java 527 | // version 1.44 and fixed in 1.46-SNAPSHOT on 2016-01-17. 528 | // Also the + sign on azi2 is a check on the normalizing of azimuths 529 | // (converting -0.0 to +0.0). 530 | GeodesicData dir = Geodesic.WGS84.Direct(90, 10, 180, -1e6); 531 | assertEquals(dir.lat2, 81.04623, 0.5e-5); 532 | assertEquals(dir.lon2, -170, 0.5e-5); 533 | assertEquals(dir.azi2, 0, 0.5e-5); 534 | assertTrue(Math.copySign(1, dir.azi2) > 0); 535 | } 536 | 537 | @Test 538 | public void GeodSolve74() { 539 | // Check fix for inaccurate areas, bug introduced in v1.46, fixed 540 | // 2015-10-16. 541 | GeodesicData inv = Geodesic.WGS84.Inverse(54.1589, 15.3872, 542 | 54.1591, 15.3877, 543 | GeodesicMask.ALL); 544 | assertEquals(inv.azi1, 55.723110355, 5e-9); 545 | assertEquals(inv.azi2, 55.723515675, 5e-9); 546 | assertEquals(inv.s12, 39.527686385, 5e-9); 547 | assertEquals(inv.a12, 0.000355495, 5e-9); 548 | assertEquals(inv.m12, 39.527686385, 5e-9); 549 | assertEquals(inv.M12, 0.999999995, 5e-9); 550 | assertEquals(inv.M21, 0.999999995, 5e-9); 551 | assertEquals(inv.S12, 286698586.30197, 5e-4); 552 | } 553 | 554 | @Test 555 | public void GeodSolve76() { 556 | // The distance from Wellington and Salamanca (a classic failure of 557 | // Vincenty) 558 | GeodesicData inv = Geodesic.WGS84.Inverse(-(41+19/60.0), 174+49/60.0, 559 | 40+58/60.0, -(5+30/60.0)); 560 | assertEquals(inv.azi1, 160.39137649664, 0.5e-11); 561 | assertEquals(inv.azi2, 19.50042925176, 0.5e-11); 562 | assertEquals(inv.s12, 19960543.857179, 0.5e-6); 563 | } 564 | 565 | @Test 566 | public void GeodSolve78() { 567 | // An example where the NGS calculator fails to converge 568 | GeodesicData inv = Geodesic.WGS84.Inverse(27.2, 0.0, -27.1, 179.5); 569 | assertEquals(inv.azi1, 45.82468716758, 0.5e-11); 570 | assertEquals(inv.azi2, 134.22776532670, 0.5e-11); 571 | assertEquals(inv.s12, 19974354.765767, 0.5e-6); 572 | } 573 | 574 | @Test 575 | public void GeodSolve80() { 576 | // Some tests to add code coverage: computing scale in special cases + zero 577 | // length geodesic (includes GeodSolve80 - GeodSolve83). 578 | GeodesicData inv = Geodesic.WGS84.Inverse(0, 0, 0, 90, 579 | GeodesicMask.GEODESICSCALE); 580 | assertEquals(inv.M12, -0.00528427534, 0.5e-10); 581 | assertEquals(inv.M21, -0.00528427534, 0.5e-10); 582 | 583 | inv = Geodesic.WGS84.Inverse(0, 0, 1e-6, 1e-6, GeodesicMask.GEODESICSCALE); 584 | assertEquals(inv.M12, 1, 0.5e-10); 585 | assertEquals(inv.M21, 1, 0.5e-10); 586 | 587 | inv = Geodesic.WGS84.Inverse(20.001, 0, 20.001, 0, GeodesicMask.ALL); 588 | assertEquals(inv.a12, 0, 1e-13); 589 | assertEquals(inv.s12, 0, 1e-8); 590 | assertEquals(inv.azi1, 180, 1e-13); 591 | assertEquals(inv.azi2, 180, 1e-13); 592 | assertEquals(inv.m12, 0, 1e-8); 593 | assertEquals(inv.M12, 1, 1e-15); 594 | assertEquals(inv.M21, 1, 1e-15); 595 | assertEquals(inv.S12, 0, 1e-10); 596 | assertTrue(Math.copySign(1, inv.a12) > 0); 597 | assertTrue(Math.copySign(1, inv.s12) > 0); 598 | assertTrue(Math.copySign(1, inv.m12) > 0); 599 | 600 | inv = Geodesic.WGS84.Inverse(90, 0, 90, 180, GeodesicMask.ALL); 601 | assertEquals(inv.a12, 0, 1e-13); 602 | assertEquals(inv.s12, 0, 1e-8); 603 | assertEquals(inv.azi1, 0, 1e-13); 604 | assertEquals(inv.azi2, 180, 1e-13); 605 | assertEquals(inv.m12, 0, 1e-8); 606 | assertEquals(inv.M12, 1, 1e-15); 607 | assertEquals(inv.M21, 1, 1e-15); 608 | assertEquals(inv.S12, 127516405431022.0, 0.5); 609 | 610 | // An incapable line which can't take distance as input 611 | GeodesicLine line = Geodesic.WGS84.Line(1, 2, 90, GeodesicMask.LATITUDE); 612 | GeodesicData dir = line.Position(1000, GeodesicMask.NONE); 613 | assertTrue(Double.isNaN(dir.a12)); 614 | } 615 | 616 | @Test 617 | public void GeodSolve84() { 618 | // Tests for python implementation to check fix for range errors with 619 | // {fmod,sin,cos}(inf) (includes GeodSolve84 - GeodSolve91). 620 | GeodesicData dir; 621 | dir = Geodesic.WGS84.Direct(0, 0, 90, Double.POSITIVE_INFINITY); 622 | assertTrue(Double.isNaN(dir.lat2)); 623 | assertTrue(Double.isNaN(dir.lon2)); 624 | assertTrue(Double.isNaN(dir.azi2)); 625 | dir = Geodesic.WGS84.Direct(0, 0, 90, Double.NaN); 626 | assertTrue(Double.isNaN(dir.lat2)); 627 | assertTrue(Double.isNaN(dir.lon2)); 628 | assertTrue(Double.isNaN(dir.azi2)); 629 | dir = Geodesic.WGS84.Direct(0, 0, Double.POSITIVE_INFINITY, 1000); 630 | assertTrue(Double.isNaN(dir.lat2)); 631 | assertTrue(Double.isNaN(dir.lon2)); 632 | assertTrue(Double.isNaN(dir.azi2)); 633 | dir = Geodesic.WGS84.Direct(0, 0, Double.NaN, 1000); 634 | assertTrue(Double.isNaN(dir.lat2)); 635 | assertTrue(Double.isNaN(dir.lon2)); 636 | assertTrue(Double.isNaN(dir.azi2)); 637 | dir = Geodesic.WGS84.Direct(0, Double.POSITIVE_INFINITY, 90, 1000); 638 | assertTrue(dir.lat2 == 0); 639 | assertTrue(Double.isNaN(dir.lon2)); 640 | assertTrue(dir.azi2 == 90); 641 | dir = Geodesic.WGS84.Direct(0, Double.NaN, 90, 1000); 642 | assertTrue(dir.lat2 == 0); 643 | assertTrue(Double.isNaN(dir.lon2)); 644 | assertTrue(dir.azi2 == 90); 645 | dir = Geodesic.WGS84.Direct(Double.POSITIVE_INFINITY, 0, 90, 1000); 646 | assertTrue(Double.isNaN(dir.lat2)); 647 | assertTrue(Double.isNaN(dir.lon2)); 648 | assertTrue(Double.isNaN(dir.azi2)); 649 | dir = Geodesic.WGS84.Direct(Double.NaN, 0, 90, 1000); 650 | assertTrue(Double.isNaN(dir.lat2)); 651 | assertTrue(Double.isNaN(dir.lon2)); 652 | assertTrue(Double.isNaN(dir.azi2)); 653 | } 654 | 655 | @Test 656 | public void GeodSolve92() { 657 | // Check fix for inaccurate hypot with python 3.[89]. Problem reported 658 | // by agdhruv https://github.com/geopy/geopy/issues/466 ; see 659 | // https://bugs.python.org/issue43088 660 | GeodesicData inv = Geodesic.WGS84.Inverse(37.757540000000006, -122.47018, 661 | 37.75754, -122.470177); 662 | assertEquals(inv.azi1, 89.99999923, 1e-7 ); 663 | assertEquals(inv.azi2, 90.00000106, 1e-7 ); 664 | assertEquals(inv.s12, 0.264, 0.5e-3); 665 | } 666 | 667 | @Test 668 | public void GeodSolve94() { 669 | // Check fix for lat2 = nan being treated as lat2 = 0 (bug found 670 | // 2021-07-26) 671 | GeodesicData inv = Geodesic.WGS84.Inverse(0, 0, Double.NaN, 90); 672 | assertTrue(Double.isNaN(inv.azi1)); 673 | assertTrue(Double.isNaN(inv.azi2)); 674 | assertTrue(Double.isNaN(inv.s12)); 675 | } 676 | 677 | @Test 678 | public void GeodSolve96() { 679 | // Failure with long doubles found with test case from Nowak + Nowak Da 680 | // Costa (2022). Problem was using somg12 > 1 as a test that it needed 681 | // to be set when roundoff could result in somg12 slightly bigger that 1. 682 | // Found + fixed 2022-03-30. 683 | Geodesic geod = new Geodesic(6378137, 1/298.257222101); 684 | GeodesicData inv = geod.Inverse(0, 0, 60.0832522871723, 89.8492185074635, 685 | GeodesicMask.AREA); 686 | assertEquals(inv.S12, 42426932221845.0, 0.5); 687 | } 688 | 689 | @Test 690 | public void Planimeter0() { 691 | // Check fix for pole-encircling bug found 2011-03-16 692 | double pa[][] = {{89, 0}, {89, 90}, {89, 180}, {89, 270}}; 693 | PolygonResult a = Planimeter(pa); 694 | assertEquals(a.perimeter, 631819.8745, 1e-4); 695 | assertEquals(a.area, 24952305678.0, 1); 696 | 697 | double pb[][] = {{-89, 0}, {-89, 90}, {-89, 180}, {-89, 270}}; 698 | a = Planimeter(pb); 699 | assertEquals(a.perimeter, 631819.8745, 1e-4); 700 | assertEquals(a.area, -24952305678.0, 1); 701 | 702 | double pc[][] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; 703 | a = Planimeter(pc); 704 | assertEquals(a.perimeter, 627598.2731, 1e-4); 705 | assertEquals(a.area, 24619419146.0, 1); 706 | 707 | double pd[][] = {{90, 0}, {0, 0}, {0, 90}}; 708 | a = Planimeter(pd); 709 | assertEquals(a.perimeter, 30022685, 1); 710 | assertEquals(a.area, 63758202715511.0, 1); 711 | a = PolyLength(pd); 712 | assertEquals(a.perimeter, 20020719, 1); 713 | assertTrue(Double.isNaN(a.area)); 714 | } 715 | 716 | @Test 717 | public void Planimeter5() { 718 | // Check fix for Planimeter pole crossing bug found 2011-06-24 719 | double points[][] = {{89, 0.1}, {89, 90.1}, {89, -179.9}}; 720 | PolygonResult a = Planimeter(points); 721 | assertEquals(a.perimeter, 539297, 1); 722 | assertEquals(a.area, 12476152838.5, 1); 723 | } 724 | 725 | @Test 726 | public void Planimeter6() { 727 | // Check fix for Planimeter lon12 rounding bug found 2012-12-03 728 | double pa[][] = {{9, -0.00000000000001}, {9, 180}, {9, 0}}; 729 | PolygonResult a = Planimeter(pa); 730 | assertEquals(a.perimeter, 36026861, 1); 731 | assertEquals(a.area, 0, 1); 732 | double pb[][] = {{9, 0.00000000000001}, {9, 0}, {9, 180}}; 733 | a = Planimeter(pb); 734 | assertEquals(a.perimeter, 36026861, 1); 735 | assertEquals(a.area, 0, 1); 736 | double pc[][] = {{9, 0.00000000000001}, {9, 180}, {9, 0}}; 737 | a = Planimeter(pc); 738 | assertEquals(a.perimeter, 36026861, 1); 739 | assertEquals(a.area, 0, 1); 740 | double pd[][] = {{9, -0.00000000000001}, {9, 0}, {9, 180}}; 741 | a = Planimeter(pd); 742 | assertEquals(a.perimeter, 36026861, 1); 743 | assertEquals(a.area, 0, 1); 744 | } 745 | 746 | @Test 747 | public void Planimeter12() { 748 | // Area of arctic circle (not really -- adjunct to rhumb-area test) 749 | double points[][] = 750 | {{66.562222222, 0}, {66.562222222, 180}, {66.562222222, 360}}; 751 | PolygonResult a = Planimeter(points); 752 | assertEquals(a.perimeter, 10465729, 1); 753 | assertEquals(a.area, 0, 1); 754 | } 755 | 756 | @Test 757 | public void Planimeter12r() { 758 | // Reverse area of arctic circle 759 | double points[][] = 760 | {{66.562222222, -0}, {66.562222222, -180}, {66.562222222, -360}}; 761 | PolygonResult a = Planimeter(points); 762 | assertEquals(a.perimeter, 10465729, 1); 763 | assertEquals(a.area, 0, 1); 764 | } 765 | 766 | @Test 767 | public void Planimeter13() { 768 | // Check encircling pole twice 769 | double points[][] = {{89,-360}, {89,-240}, {89,-120}, 770 | {89,0}, {89,120}, {89,240}}; 771 | PolygonResult a = Planimeter(points); 772 | assertEquals(a.perimeter, 1160741, 1); 773 | assertEquals(a.area, 32415230256.0, 1); 774 | } 775 | 776 | @Test 777 | public void Planimeter15() { 778 | // Coverage tests, includes Planimeter15 - Planimeter18 (combinations of 779 | // reverse and sign) + calls to testpoint, testedge. 780 | PolygonResult a; 781 | double lat[] = {2, 1, 3}, lon[] = {1, 2, 3}; 782 | double r = 18454562325.45119, 783 | a0 = 510065621724088.5093; // ellipsoid area 784 | polygon.Clear(); 785 | polygon.AddPoint(lat[0], lon[0]); 786 | polygon.AddPoint(lat[1], lon[1]); 787 | a = polygon.TestPoint(lat[2], lon[2], false, true); 788 | assertEquals(a.area, r, 0.5); 789 | a = polygon.TestPoint(lat[2], lon[2], false, false); 790 | assertEquals(a.area, r, 0.5); 791 | a = polygon.TestPoint(lat[2], lon[2], true, true); 792 | assertEquals(a.area, -r, 0.5); 793 | a = polygon.TestPoint(lat[2], lon[2], true, false); 794 | assertEquals(a.area, a0-r, 0.5); 795 | GeodesicData inv = Geodesic.WGS84.Inverse(lat[1], lon[1], lat[2], lon[2]); 796 | a = polygon.TestEdge(inv.azi1, inv.s12, false, true); 797 | assertEquals(a.area, r, 0.5); 798 | a = polygon.TestEdge(inv.azi1, inv.s12, false, false); 799 | assertEquals(a.area, r, 0.5); 800 | a = polygon.TestEdge(inv.azi1, inv.s12, true, true); 801 | assertEquals(a.area, -r, 0.5); 802 | a = polygon.TestEdge(inv.azi1, inv.s12, true, false); 803 | assertEquals(a.area, a0-r, 0.5); 804 | polygon.AddPoint(lat[2], lon[2]); 805 | a = polygon.Compute(false, true); 806 | assertEquals(a.area, r, 0.5); 807 | a = polygon.Compute(false, false); 808 | assertEquals(a.area, r, 0.5); 809 | a = polygon.Compute(true, true); 810 | assertEquals(a.area, -r, 0.5); 811 | a = polygon.Compute(true, false); 812 | assertEquals(a.area, a0-r, 0.5); 813 | } 814 | 815 | @Test 816 | public void Planimeter19() { 817 | // Coverage tests, includes Planimeter19 - Planimeter20 (degenerate 818 | // polygons) + extra cases. 819 | PolygonResult a; 820 | polygon.Clear(); 821 | a = polygon.Compute(false, true); 822 | assertTrue(a.area == 0); 823 | assertTrue(a.perimeter == 0); 824 | a = polygon.TestPoint(1, 1, false, true); 825 | assertTrue(a.area == 0); 826 | assertTrue(a.perimeter == 0); 827 | a = polygon.TestEdge(90, 1000, false, true); 828 | assertTrue(Double.isNaN(a.area)); 829 | assertTrue(Double.isNaN(a.perimeter)); 830 | polygon.AddPoint(1, 1); 831 | a = polygon.Compute(false, true); 832 | assertTrue(a.area == 0); 833 | assertTrue(a.perimeter == 0); 834 | polyline.Clear(); 835 | a = polyline.Compute(false, true); 836 | assertTrue(a.perimeter == 0); 837 | a = polyline.TestPoint(1, 1, false, true); 838 | assertTrue(a.perimeter == 0); 839 | a = polyline.TestEdge(90, 1000, false, true); 840 | assertTrue(Double.isNaN(a.perimeter)); 841 | polyline.AddPoint(1, 1); 842 | a = polyline.Compute(false, true); 843 | assertTrue(a.perimeter == 0); 844 | polygon.AddPoint(1, 1); 845 | a = polyline.TestEdge(90, 1000, false, true); 846 | assertEquals(a.perimeter, 1000, 1e-10); 847 | a = polyline.TestPoint(2, 2, false, true); 848 | assertEquals(a.perimeter, 156876.149, 0.5e-3); 849 | } 850 | 851 | @Test 852 | public void Planimeter21() { 853 | // Some tests to add code coverage: multiple circlings of pole (includes 854 | // Planimeter21 - Planimeter28) + invocations via testpoint and testedge. 855 | PolygonResult a; 856 | double lat = 45, 857 | azi = 39.2144607176828184218, s = 8420705.40957178156285, 858 | r = 39433884866571.4277, // Area for one circuit 859 | a0 = 510065621724088.5093; // Ellipsoid area 860 | int i; 861 | polygon.Clear(); 862 | polygon.AddPoint(lat, 60); 863 | polygon.AddPoint(lat, 180); 864 | polygon.AddPoint(lat, -60); 865 | polygon.AddPoint(lat, 60); 866 | polygon.AddPoint(lat, 180); 867 | polygon.AddPoint(lat, -60); 868 | for (i = 3; i <= 4; ++i) { 869 | polygon.AddPoint(lat, 60); 870 | polygon.AddPoint(lat, 180); 871 | a = polygon.TestPoint(lat, -60, false, true); 872 | assertEquals(a.area, i*r, 0.5); 873 | a = polygon.TestPoint(lat, -60, false, false); 874 | assertEquals(a.area, i*r, 0.5); 875 | a = polygon.TestPoint(lat, -60, true, true); 876 | assertEquals(a.area, -i*r, 0.5); 877 | a = polygon.TestPoint(lat, -60, true, false); 878 | assertEquals(a.area, -i*r + a0, 0.5); 879 | a = polygon.TestEdge(azi, s, false, true); 880 | assertEquals(a.area, i*r, 0.5); 881 | a = polygon.TestEdge(azi, s, false, false); 882 | assertEquals(a.area, i*r, 0.5); 883 | a = polygon.TestEdge(azi, s, true, true); 884 | assertEquals(a.area, -i*r, 0.5); 885 | a = polygon.TestEdge(azi, s, true, false); 886 | assertEquals(a.area, -i*r + a0, 0.5); 887 | polygon.AddPoint(lat, -60); 888 | a = polygon.Compute(false, true); 889 | assertEquals(a.area, i*r, 0.5); 890 | a = polygon.Compute(false, false); 891 | assertEquals(a.area, i*r, 0.5); 892 | a = polygon.Compute(true, true); 893 | assertEquals(a.area, -i*r, 0.5); 894 | a = polygon.Compute(true, false); 895 | assertEquals(a.area, -i*r + a0, 0.5); 896 | } 897 | } 898 | 899 | @Test 900 | public void Planimeter29() { 901 | // Check fix to transitdirect vs transit zero handling inconsistency 902 | PolygonResult a; 903 | polygon.Clear(); 904 | polygon.AddPoint(0, 0); 905 | polygon.AddEdge( 90, 1000); 906 | polygon.AddEdge( 0, 1000); 907 | polygon.AddEdge(-90, 1000); 908 | a = polygon.Compute(false, true); 909 | // The area should be 1e6. Prior to the fix it was 1e6 - A/2, where 910 | // A = ellipsoid area. 911 | assertEquals(a.area, 1000000.0, 0.01); 912 | } 913 | } 914 | -------------------------------------------------------------------------------- /src/test/java/net/sf/geographiclib/SignTest.java: -------------------------------------------------------------------------------- 1 | package net.sf.geographiclib; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import org.junit.Test; 6 | 7 | public class SignTest { 8 | 9 | private static boolean equiv(double x, double y) { 10 | // Test for equivalence 11 | return (Double.isNaN(x) && Double.isNaN(y)) || 12 | (x == y && Math.copySign(1, x) == Math.copySign(1, y)); 13 | } 14 | 15 | @Test 16 | public void test_AngRound() { 17 | // Test special cases for AngRound 18 | double eps = Math.ulp(1.0); 19 | assertTrue(equiv(GeoMath.AngRound(-eps/32), -eps/32)); 20 | assertTrue(equiv(GeoMath.AngRound(-eps/64), -0.0 )); 21 | assertTrue(equiv(GeoMath.AngRound(- 0.0 ), -0.0 )); 22 | assertTrue(equiv(GeoMath.AngRound( 0.0 ), +0.0 )); 23 | assertTrue(equiv(GeoMath.AngRound( eps/64), +0.0 )); 24 | assertTrue(equiv(GeoMath.AngRound( eps/32), +eps/32)); 25 | assertTrue(equiv(GeoMath.AngRound((1-2*eps)/64), (1-2*eps)/64)); 26 | assertTrue(equiv(GeoMath.AngRound((1-eps )/64), 1.0 /64)); 27 | assertTrue(equiv(GeoMath.AngRound((1-eps/2)/64), 1.0 /64)); 28 | assertTrue(equiv(GeoMath.AngRound((1-eps/4)/64), 1.0 /64)); 29 | assertTrue(equiv(GeoMath.AngRound( 1.0 /64), 1.0 /64)); 30 | assertTrue(equiv(GeoMath.AngRound((1+eps/2)/64), 1.0 /64)); 31 | assertTrue(equiv(GeoMath.AngRound((1+eps )/64), 1.0 /64)); 32 | assertTrue(equiv(GeoMath.AngRound((1+2*eps)/64), (1+2*eps)/64)); 33 | assertTrue(equiv(GeoMath.AngRound((1-eps )/32), (1-eps )/32)); 34 | assertTrue(equiv(GeoMath.AngRound((1-eps/2)/32), 1.0 /32)); 35 | assertTrue(equiv(GeoMath.AngRound((1-eps/4)/32), 1.0 /32)); 36 | assertTrue(equiv(GeoMath.AngRound( 1.0 /32), 1.0 /32)); 37 | assertTrue(equiv(GeoMath.AngRound((1+eps/2)/32), 1.0 /32)); 38 | assertTrue(equiv(GeoMath.AngRound((1+eps )/32), (1+eps )/32)); 39 | assertTrue(equiv(GeoMath.AngRound((1-eps )/16), (1-eps )/16)); 40 | assertTrue(equiv(GeoMath.AngRound((1-eps/2)/16), (1-eps/2)/16)); 41 | assertTrue(equiv(GeoMath.AngRound((1-eps/4)/16), 1.0 /16)); 42 | assertTrue(equiv(GeoMath.AngRound( 1.0 /16), 1.0 /16)); 43 | assertTrue(equiv(GeoMath.AngRound((1+eps/4)/16), 1.0 /16)); 44 | assertTrue(equiv(GeoMath.AngRound((1+eps/2)/16), 1.0 /16)); 45 | assertTrue(equiv(GeoMath.AngRound((1+eps )/16), (1+eps )/16)); 46 | assertTrue(equiv(GeoMath.AngRound((1-eps )/ 8), (1-eps )/ 8)); 47 | assertTrue(equiv(GeoMath.AngRound((1-eps/2)/ 8), (1-eps/2)/ 8)); 48 | assertTrue(equiv(GeoMath.AngRound((1-eps/4)/ 8), 1.0 / 8)); 49 | assertTrue(equiv(GeoMath.AngRound((1+eps/2)/ 8), 1.0 / 8)); 50 | assertTrue(equiv(GeoMath.AngRound((1+eps )/ 8), (1+eps )/ 8)); 51 | assertTrue(equiv(GeoMath.AngRound( 1-eps ), 1-eps )); 52 | assertTrue(equiv(GeoMath.AngRound( 1-eps/2 ), 1-eps/2 )); 53 | assertTrue(equiv(GeoMath.AngRound( 1-eps/4 ), 1 )); 54 | assertTrue(equiv(GeoMath.AngRound( 1.0 ), 1 )); 55 | assertTrue(equiv(GeoMath.AngRound( 1+eps/4 ), 1 )); 56 | assertTrue(equiv(GeoMath.AngRound( 1+eps/2 ), 1 )); 57 | assertTrue(equiv(GeoMath.AngRound( 1+eps ), 1+ eps )); 58 | assertTrue(equiv(GeoMath.AngRound( 90.0-64*eps), 90-64*eps )); 59 | assertTrue(equiv(GeoMath.AngRound( 90.0-32*eps), 90 )); 60 | assertTrue(equiv(GeoMath.AngRound( 90.0 ), 90 )); 61 | } 62 | 63 | @Test 64 | public void test_sincosd() { 65 | // Test special cases for sincosd 66 | double inf = Double.POSITIVE_INFINITY, nan = Double.NaN; 67 | Pair p = new Pair(); 68 | GeoMath.sincosd(p, - inf); 69 | assertTrue(equiv(p.first, nan) && equiv(p.second, nan)); 70 | GeoMath.sincosd(p, -810.0); 71 | assertTrue(equiv(p.first, -1.0) && equiv(p.second, +0.0)); 72 | GeoMath.sincosd(p, -720.0); 73 | assertTrue(equiv(p.first, -0.0) && equiv(p.second, +1.0)); 74 | GeoMath.sincosd(p, -630.0); 75 | assertTrue(equiv(p.first, +1.0) && equiv(p.second, +0.0)); 76 | GeoMath.sincosd(p, -540.0); 77 | assertTrue(equiv(p.first, -0.0) && equiv(p.second, -1.0)); 78 | GeoMath.sincosd(p, -450.0); 79 | assertTrue(equiv(p.first, -1.0) && equiv(p.second, +0.0)); 80 | GeoMath.sincosd(p, -360.0); 81 | assertTrue(equiv(p.first, -0.0) && equiv(p.second, +1.0)); 82 | GeoMath.sincosd(p, -270.0); 83 | assertTrue(equiv(p.first, +1.0) && equiv(p.second, +0.0)); 84 | GeoMath.sincosd(p, -180.0); 85 | assertTrue(equiv(p.first, -0.0) && equiv(p.second, -1.0)); 86 | GeoMath.sincosd(p, - 90.0); 87 | assertTrue(equiv(p.first, -1.0) && equiv(p.second, +0.0)); 88 | GeoMath.sincosd(p, - 0.0); 89 | assertTrue(equiv(p.first, -0.0) && equiv(p.second, +1.0)); 90 | GeoMath.sincosd(p, + 0.0); 91 | assertTrue(equiv(p.first, +0.0) && equiv(p.second, +1.0)); 92 | GeoMath.sincosd(p, + 90.0); 93 | assertTrue(equiv(p.first, +1.0) && equiv(p.second, +0.0)); 94 | GeoMath.sincosd(p, +180.0); 95 | assertTrue(equiv(p.first, +0.0) && equiv(p.second, -1.0)); 96 | GeoMath.sincosd(p, +270.0); 97 | assertTrue(equiv(p.first, -1.0) && equiv(p.second, +0.0)); 98 | GeoMath.sincosd(p, +360.0); 99 | assertTrue(equiv(p.first, +0.0) && equiv(p.second, +1.0)); 100 | GeoMath.sincosd(p, +450.0); 101 | assertTrue(equiv(p.first, +1.0) && equiv(p.second, +0.0)); 102 | GeoMath.sincosd(p, +540.0); 103 | assertTrue(equiv(p.first, +0.0) && equiv(p.second, -1.0)); 104 | GeoMath.sincosd(p, +630.0); 105 | assertTrue(equiv(p.first, -1.0) && equiv(p.second, +0.0)); 106 | GeoMath.sincosd(p, +720.0); 107 | assertTrue(equiv(p.first, +0.0) && equiv(p.second, +1.0)); 108 | GeoMath.sincosd(p, +810.0); 109 | assertTrue(equiv(p.first, +1.0) && equiv(p.second, +0.0)); 110 | GeoMath.sincosd(p, + inf); 111 | assertTrue(equiv(p.first, nan) && equiv(p.second, nan)); 112 | GeoMath.sincosd(p, nan); 113 | assertTrue(equiv(p.first, nan) && equiv(p.second, nan)); 114 | 115 | /// Test accuracy of sincosd 116 | double s1, c1, s2, c2, s3, c3; 117 | GeoMath.sincosd(p, 9.0); s1 = p.first; c1 = p.second; 118 | GeoMath.sincosd(p, 81.0); s2 = p.first; c2 = p.second; 119 | GeoMath.sincosd(p, -123456789.0); s3 = p.first; c3 = p.second; 120 | assertTrue(equiv(s1, c2)); 121 | assertTrue(equiv(s1, s3)); 122 | assertTrue(equiv(c1, s2)); 123 | assertTrue(equiv(c1,-c3)); 124 | } 125 | 126 | @Test 127 | public void test_atan2d() { 128 | // Test special cases for atan2d 129 | double inf = Double.POSITIVE_INFINITY, nan = Double.NaN; 130 | assertTrue(equiv(GeoMath.atan2d(+0.0 , -0.0 ), +180)); 131 | assertTrue(equiv(GeoMath.atan2d(-0.0 , -0.0 ), -180)); 132 | assertTrue(equiv(GeoMath.atan2d(+0.0 , +0.0 ), +0.0)); 133 | assertTrue(equiv(GeoMath.atan2d(-0.0 , +0.0 ), -0.0)); 134 | assertTrue(equiv(GeoMath.atan2d(+0.0 , -1.0 ), +180)); 135 | assertTrue(equiv(GeoMath.atan2d(-0.0 , -1.0 ), -180)); 136 | assertTrue(equiv(GeoMath.atan2d(+0.0 , +1.0 ), +0.0)); 137 | assertTrue(equiv(GeoMath.atan2d(-0.0 , +1.0 ), -0.0)); 138 | assertTrue(equiv(GeoMath.atan2d(-1.0 , +0.0 ), -90)); 139 | assertTrue(equiv(GeoMath.atan2d(-1.0 , -0.0 ), -90)); 140 | assertTrue(equiv(GeoMath.atan2d(+1.0 , +0.0 ), +90)); 141 | assertTrue(equiv(GeoMath.atan2d(+1.0 , -0.0 ), +90)); 142 | assertTrue(equiv(GeoMath.atan2d(+1.0 , -inf), +180)); 143 | assertTrue(equiv(GeoMath.atan2d(-1.0 , -inf), -180)); 144 | assertTrue(equiv(GeoMath.atan2d(+1.0 , +inf), +0.0)); 145 | assertTrue(equiv(GeoMath.atan2d(-1.0 , +inf), -0.0)); 146 | assertTrue(equiv(GeoMath.atan2d( +inf, +1.0 ), +90)); 147 | assertTrue(equiv(GeoMath.atan2d( +inf, -1.0 ), +90)); 148 | assertTrue(equiv(GeoMath.atan2d( -inf, +1.0 ), -90)); 149 | assertTrue(equiv(GeoMath.atan2d( -inf, -1.0 ), -90)); 150 | assertTrue(equiv(GeoMath.atan2d( +inf, -inf), +135)); 151 | assertTrue(equiv(GeoMath.atan2d( -inf, -inf), -135)); 152 | assertTrue(equiv(GeoMath.atan2d( +inf, +inf), +45)); 153 | assertTrue(equiv(GeoMath.atan2d( -inf, +inf), -45)); 154 | assertTrue(equiv(GeoMath.atan2d( nan, +1.0 ), nan)); 155 | assertTrue(equiv(GeoMath.atan2d(+1.0 , nan), nan)); 156 | 157 | // Test accuracy of atan2d 158 | double s = 7e-16; 159 | assertEquals(GeoMath.atan2d(s, -1.0), 180 - GeoMath.atan2d(s, 1.0), 0); 160 | } 161 | 162 | @Test 163 | public void test_sum() { 164 | // Test special cases of sum 165 | Pair p = new Pair(); 166 | GeoMath.sum(p, +9.0, -9.0); assertTrue(equiv(p.first, +0.0)); 167 | GeoMath.sum(p, -9.0, +9.0); assertTrue(equiv(p.first, +0.0)); 168 | GeoMath.sum(p, -0.0, +0.0); assertTrue(equiv(p.first, +0.0)); 169 | GeoMath.sum(p, +0.0, -0.0); assertTrue(equiv(p.first, +0.0)); 170 | GeoMath.sum(p, -0.0, -0.0); assertTrue(equiv(p.first, -0.0)); 171 | GeoMath.sum(p, +0.0, +0.0); assertTrue(equiv(p.first, +0.0)); 172 | } 173 | 174 | @Test 175 | public void test_AngNormalize() { 176 | // Test special cases of AngNormalize 177 | assertTrue(equiv(GeoMath.AngNormalize(-900.0), -180)); 178 | assertTrue(equiv(GeoMath.AngNormalize(-720.0), -0.0)); 179 | assertTrue(equiv(GeoMath.AngNormalize(-540.0), -180)); 180 | assertTrue(equiv(GeoMath.AngNormalize(-360.0), -0.0)); 181 | assertTrue(equiv(GeoMath.AngNormalize(-180.0), -180)); 182 | assertTrue(equiv(GeoMath.AngNormalize( -0.0), -0.0)); 183 | assertTrue(equiv(GeoMath.AngNormalize( +0.0), +0.0)); 184 | assertTrue(equiv(GeoMath.AngNormalize( 180.0), +180)); 185 | assertTrue(equiv(GeoMath.AngNormalize( 360.0), +0.0)); 186 | assertTrue(equiv(GeoMath.AngNormalize( 540.0), +180)); 187 | assertTrue(equiv(GeoMath.AngNormalize( 720.0), +0.0)); 188 | assertTrue(equiv(GeoMath.AngNormalize( 900.0), +180)); 189 | } 190 | 191 | @Test 192 | public void test_AngDiff() { 193 | // Test special cases of AngDiff 194 | double eps = Math.ulp(1.0); 195 | Pair p = new Pair(); 196 | GeoMath.AngDiff(p, + 0.0,+ 0.0); assertTrue(equiv(p.first,+0.0 )); 197 | GeoMath.AngDiff(p, + 0.0,- 0.0); assertTrue(equiv(p.first,-0.0 )); 198 | GeoMath.AngDiff(p, - 0.0,+ 0.0); assertTrue(equiv(p.first,+0.0 )); 199 | GeoMath.AngDiff(p, - 0.0,- 0.0); assertTrue(equiv(p.first,+0.0 )); 200 | GeoMath.AngDiff(p, + 5.0,+365.0); assertTrue(equiv(p.first,+0.0 )); 201 | GeoMath.AngDiff(p, +365.0,+ 5.0); assertTrue(equiv(p.first,-0.0 )); 202 | GeoMath.AngDiff(p, + 5.0,+185.0); assertTrue(equiv(p.first,+180.0)); 203 | GeoMath.AngDiff(p, +185.0,+ 5.0); assertTrue(equiv(p.first,-180.0)); 204 | GeoMath.AngDiff(p, +eps ,+180.0); assertTrue(equiv(p.first,+180.0)); 205 | GeoMath.AngDiff(p, -eps ,+180.0); assertTrue(equiv(p.first,-180.0)); 206 | GeoMath.AngDiff(p, +eps ,-180.0); assertTrue(equiv(p.first,+180.0)); 207 | GeoMath.AngDiff(p, -eps ,-180.0); assertTrue(equiv(p.first,-180.0)); 208 | 209 | // Test accuracy of AngDiff 210 | double x = 138 + 128 * eps, y = -164; GeoMath.AngDiff(p, x, y); 211 | assertEquals(p.first, 58 - 128 * eps, 0); 212 | } 213 | 214 | @Test 215 | public void test_equatorial_coincident() { 216 | // azimuth with coincident point on equator 217 | // lat1 lat2 azi1/2 218 | double C[][] = { 219 | { +0.0, -0.0, 180 }, 220 | { -0.0, +0.0, 0 } 221 | }; 222 | for (int i = 0; i < C.length; ++i) { 223 | GeodesicData inv = Geodesic.WGS84.Inverse(C[i][0], 0.0, C[i][1], 0.0); 224 | assertTrue(equiv(inv.azi1, C[i][2])); 225 | assertTrue(equiv(inv.azi2, C[i][2])); 226 | } 227 | } 228 | 229 | @Test 230 | public void test_equatorial_NS() { 231 | // Does the nearly antipodal equatorial solution go north or south? 232 | // lat1 lat2 azi1 azi2 233 | double C[][] = { 234 | { +0.0, +0.0, 56, 124}, 235 | { -0.0, -0.0, 124, 56} 236 | }; 237 | for (int i = 0; i < C.length; ++i) { 238 | GeodesicData inv = Geodesic.WGS84.Inverse(C[i][0], 0.0, C[i][1], 179.5); 239 | assertEquals(inv.azi1, C[i][2], 1); 240 | assertEquals(inv.azi2, C[i][3], 1); 241 | } 242 | } 243 | 244 | @Test 245 | public void test_antipodal() { 246 | // How does the exact antipodal equatorial path go N/S + E/W""" 247 | // lat1 lat2 lon2 azi1 azi2 248 | double C[][] = { 249 | { +0.0, +0.0, +180, +0.0, +180}, 250 | { -0.0, -0.0, +180, +180, +0.0}, 251 | { +0.0, +0.0, -180, -0.0, -180}, 252 | { -0.0, -0.0, -180, -180, -0.0} 253 | }; 254 | for (int i = 0; i < C.length; ++i) { 255 | GeodesicData inv = Geodesic.WGS84.Inverse(C[i][0], 0.0, C[i][1], C[i][2]); 256 | assertTrue(equiv(inv.azi1, C[i][3])); 257 | assertTrue(equiv(inv.azi2, C[i][4])); 258 | } 259 | } 260 | 261 | @Test 262 | public void test_antipodal_prolate() { 263 | // Antipodal points on the equator with prolate ellipsoid 264 | // lon2 azi1/2 265 | double C[][] = { 266 | { +180, +90 }, 267 | { -180, -90 } 268 | }; 269 | Geodesic geod = new Geodesic(6.4e6, -1/300.0); 270 | for (int i = 0; i < C.length; ++i) { 271 | GeodesicData inv = geod.Inverse(0.0, 0.0, 0.0, C[i][0]); 272 | assertTrue(equiv(inv.azi1, C[i][1])); 273 | assertTrue(equiv(inv.azi2, C[i][1])); 274 | } 275 | } 276 | 277 | @Test 278 | public void test_azimuth_0_180() { 279 | // azimuths = +/-0 and +/-180 for the direct problem 280 | // azi1, lon2, azi2 281 | double C[][] = { 282 | { +0.0, +180, +180 }, 283 | { -0.0, -180, -180 }, 284 | { +180, +180, +0.0 }, 285 | { -180, -180, -0.0 } 286 | }; 287 | for (int i = 0; i < C.length; ++i) { 288 | GeodesicData dir = 289 | Geodesic.WGS84.Direct(0.0, 0.0, C[i][0], 15e6, 290 | GeodesicMask.STANDARD | GeodesicMask.LONG_UNROLL); 291 | assertTrue(equiv(dir.lon2, C[i][1])); 292 | assertTrue(equiv(dir.azi2, C[i][2])); 293 | } 294 | } 295 | } 296 | --------------------------------------------------------------------------------