├── .github ├── pull_request_template.md └── workflows │ └── test.yml ├── .gitignore ├── CHANGES.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── geodesic_benchmark.rs ├── bors.toml ├── examples └── direct.rs ├── script └── download-test-data.sh ├── src ├── bin │ └── geodsolve.rs ├── geodesic.rs ├── geodesic_capability.rs ├── geodesic_line.rs ├── geomath.rs ├── lib.rs └── polygon_area.rs └── test_fixtures └── GeodTest-100.dat /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | - [ ] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/master/CODE_OF_CONDUCT.md). 2 | - [ ] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users. 3 | --- 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | - push 4 | - pull_request 5 | - merge_group 6 | jobs: 7 | linting: 8 | name: formatting and clippy 9 | runs-on: ubuntu-latest 10 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: dtolnay/rust-toolchain@stable 14 | with: 15 | components: clippy, rustfmt 16 | - run: cargo fmt --all -- --check 17 | - run: cargo clippy --all-features --all-targets -- -Dwarnings 18 | tests: 19 | name: tests 20 | runs-on: ubuntu-latest 21 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: dtolnay/rust-toolchain@stable 25 | - run: script/download-test-data.sh 26 | - run: cargo build --all-targets 27 | - run: cargo test 28 | - run: cargo test --no-default-features 29 | - run: cargo test --features test_full 30 | - run: cargo bench --features test_full 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | **/Cargo.lock 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | __pycache__ 10 | 11 | # Folder for holding potentially large test data files. 12 | /test_fixtures/test_data_unzipped 13 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 0.2.5 2 | 3 | * Make `GeodesicLine` public, which can be more efficient when placing multiple points along a single line. 4 | * 5 | * Thanks to @JMdoubleU for their work here. 6 | 7 | ## 0.2.4 8 | 9 | * Performance improvements for direct and indirect geodesic calculations 10 | * Remove lazy_static dependency 11 | * Thanks to contributor @valarauca for their work on performance and code cleanup. 12 | 13 | ## 0.2.3 14 | 15 | ### New Features 16 | 17 | * Added `PolygonArea` to allow calculating perimeter and area of a polygon on a geodesic. 18 | * Added `accurate` feature (enabled by default) for highly accurate `PolygonArea` calculations. 19 | 20 | ## 0.2.2 21 | 22 | This patch release includes only small clippy/lint fixes, and should not 23 | appreciably affect behavior. Thanks to @Quba1 for the cleanup! 24 | 25 | - 26 | 27 | ## 0.2.1 28 | 29 | This patch release has no API changes, but fixes some correctness issues around 30 | some of the edge cases represented in Karney's GeodSolveXX tests. 31 | 32 | My gratitude to Charles Karney for publishing good tests and especially to 33 | Stony Lohr (@stonylohr) for implementing them here. 34 | 35 | And also thank you to Stony for building out our CI and your other project 36 | maintenance work! 37 | 38 | <3 39 | 40 | ### Fixed 41 | 42 | * Correctness fixes 43 | - 44 | - 45 | - 46 | - 47 | 48 | ### Internal Work 49 | 50 | * Set up CI to run against Karney's GeodTest data 51 | - 52 | - 53 | 54 | * Fix bug in tests 55 | - 56 | - 57 | 58 | * Code cleanup 59 | - 60 | 61 | ## 0.2.0 62 | 63 | * Initial release 64 | 65 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "geographiclib-rs" 3 | version = "0.2.5" 4 | license = "MIT" 5 | edition = "2018" 6 | rust-version = "1.70.0" 7 | 8 | description = "A port of geographiclib in Rust." 9 | repository = "https://github.com/georust/geographiclib-rs" 10 | keywords = ["gis", "geo", "geography", "geospatial"] 11 | documentation = "https://docs.rs/geographiclib-rs" 12 | readme = "README.md" 13 | 14 | [features] 15 | # Run tests against Karney's GeodTest.dat test cases. 16 | # You must run script/download-test-data.sh before using this feature 17 | test_full = [] 18 | 19 | # Run tests against Karney's abridged GeodTest-short.dat test cases. 20 | # You must run script/download-test-data.sh before using this feature 21 | test_short = [] 22 | 23 | default = ["accurate"] 24 | 25 | [dependencies] 26 | accurate = { version = "0.3", optional = true, default-features = false } 27 | libm = { version = "0.2.8", default-features = false } 28 | 29 | [dev-dependencies] 30 | approx = "0.5.1" 31 | criterion = "0.4.0" 32 | geographiclib = "0.1.0" 33 | 34 | [[bench]] 35 | name = "geodesic_benchmark" 36 | harness = false 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geographiclib-rs 2 | 3 | A subset of [geographiclib](https://geographiclib.sourceforge.io/) implemented in Rust. 4 | 5 | [Documentation](https://docs.rs/geographiclib-rs) 6 | 7 | Currently this implements the direct and the inverse geodesic calculations. 8 | 9 | If instead you are looking for Rust bindings to [Karney's C++ implementation](https://sourceforge.net/projects/geographiclib/), see [https://crates.io/geographiclib](https://crates.io/crates/geographiclib). 10 | 11 | ## Examples 12 | 13 | ```rust 14 | // Determine the point 10000 km NE of JFK - the "direct" geodesic calculation. 15 | use geographiclib_rs::{Geodesic, DirectGeodesic}; 16 | 17 | let g = Geodesic::wgs84(); 18 | let jfk_lat = 40.64; 19 | let jfk_lon = -73.78; 20 | let northeast_azimuth = 45.0; 21 | 22 | let (lat, lon, az) = g.direct(jfk_lat, jfk_lon, northeast_azimuth, 10e6); 23 | 24 | use approx::assert_relative_eq; 25 | assert_relative_eq!(lat, 32.621100463725796); 26 | assert_relative_eq!(lon, 49.05248709295982); 27 | assert_relative_eq!(az, 140.4059858768007); 28 | ``` 29 | 30 | ```rust 31 | // Determine the distance between two points - the "inverse" geodesic calculation. 32 | use geographiclib_rs::{Geodesic, InverseGeodesic}; 33 | 34 | let g = Geodesic::wgs84(); 35 | let p1 = (34.095925, -118.2884237); 36 | let p2 = (59.4323439, 24.7341649); 37 | let s12: f64 = g.inverse(p1.0, p1.1, p2.0, p2.1); 38 | 39 | use approx::assert_relative_eq; 40 | assert_relative_eq!(s12, 9094718.72751138); 41 | ``` 42 | 43 | ```rust 44 | // Determine the perimeter and area of a polygon. 45 | use geographiclib_rs::{Geodesic, PolygonArea, Winding}; 46 | 47 | let g = Geodesic::wgs84(); 48 | let mut pa = PolygonArea::new(&g, Winding::CounterClockwise); 49 | pa.add_point(0.0, 0.0); 50 | pa.add_point(0.0, 1.0); 51 | pa.add_point(1.0, 1.0); 52 | pa.add_point(1.0, 0.0); 53 | 54 | let (perimeter_m, area_m_squared, num_points) = pa.compute(false); 55 | 56 | use approx::assert_relative_eq; 57 | assert_relative_eq!(perimeter_m, 443770.91724830196); 58 | assert_relative_eq!(area_m_squared, 12308778361.469452); 59 | assert_eq!(num_points, 4); 60 | ``` 61 | 62 | ```rust 63 | // Determine the distance between rovers Pathfinder and Curiosity on Mars 64 | use geographiclib_rs::{Geodesic, InverseGeodesic}; 65 | 66 | let mars = Geodesic::new(3396190.0, 1.0 / 169.8944472); 67 | let pathfinder = (19.26, 326.75); 68 | let curiosity = (-4.765700445, 137.39820983); 69 | let distance_m: f64 = mars.inverse(curiosity.0, curiosity.1, pathfinder.0, pathfinder.1); 70 | 71 | assert_eq!(distance_m.round(), 9639113.0); 72 | ``` 73 | 74 | ## Features 75 | 76 | 1. `accurate`: Enabled by default. Use the [`accurate`](https://docs.rs/accurate/latest/accurate/) crate to provide high accuracy polygon areas and perimeters in `PolygonArea`. Can be disabled for better performance or when `PolygonArea` is not being used. 77 | 78 | ## Benchmarking 79 | 80 | To compare the direct and inverse geodesic calculation against the [geographiclib c bindings](https://github.com/savage13/geographiclib), run: 81 | 82 | ```shell 83 | cargo bench 84 | ``` 85 | 86 | Which produces output like: 87 | 88 | ```text 89 | direct (c wrapper)/default 90 | time: [24.046 µs 24.071 µs 24.099 µs] 91 | 92 | direct (rust impl)/default 93 | time: [26.129 µs 26.168 µs 26.211 µs] 94 | 95 | inverse (c wrapper)/default 96 | time: [45.061 µs 45.141 µs 45.227 µs] 97 | 98 | inverse (rust impl)/default 99 | time: [67.739 µs 67.796 µs 67.865 µs] 100 | ``` 101 | 102 | Showing that, at least in this benchmark, the Rust implementation is 10-50% slower than the c bindings. 103 | -------------------------------------------------------------------------------- /benches/geodesic_benchmark.rs: -------------------------------------------------------------------------------- 1 | extern crate criterion; 2 | extern crate geographiclib; 3 | extern crate geographiclib_rs; 4 | 5 | use criterion::{criterion_group, criterion_main, Criterion}; 6 | use std::time::Duration; 7 | 8 | use geographiclib_rs::{DirectGeodesic, InverseGeodesic}; 9 | use std::fs::File; 10 | use std::io::{BufRead, BufReader}; 11 | 12 | const TEST_MODE_FULL: &str = "full"; 13 | const TEST_MODE_SHORT: &str = "short"; 14 | const TEST_MODE_DEFAULT: &str = "default"; 15 | 16 | const FULL_TEST_PATH: &str = "test_fixtures/test_data_unzipped/GeodTest.dat"; 17 | const SHORT_TEST_PATH: &str = "test_fixtures/test_data_unzipped/GeodTest-short.dat"; 18 | const BUILTIN_TEST_PATH: &str = "test_fixtures/GeodTest-100.dat"; 19 | fn test_input_path() -> (&'static str, &'static str) { 20 | if cfg!(feature = "test_full") { 21 | (TEST_MODE_FULL, FULL_TEST_PATH) 22 | } else if cfg!(feature = "test_short") { 23 | (TEST_MODE_SHORT, SHORT_TEST_PATH) 24 | } else { 25 | (TEST_MODE_DEFAULT, BUILTIN_TEST_PATH) 26 | } 27 | } 28 | 29 | fn geodesic_direct_benchmark(c: &mut Criterion) { 30 | let (mode, file_path) = test_input_path(); 31 | let file = File::open(file_path).unwrap(); 32 | let reader = BufReader::new(file); 33 | let inputs: Vec<(f64, f64, f64, f64)> = reader 34 | .lines() 35 | .map(|line| { 36 | let line = line.unwrap(); 37 | let fields: Vec = line.split(' ').map(|s| s.parse::().unwrap()).collect(); 38 | (fields[0], fields[1], fields[2], fields[6]) 39 | }) 40 | .collect(); 41 | 42 | { 43 | let mut group = c.benchmark_group("direct (c wrapper)"); 44 | if mode == TEST_MODE_FULL { 45 | group.measurement_time(Duration::from_secs(30)); 46 | } 47 | group.bench_function(mode, |b| { 48 | let geod = geographiclib::Geodesic::wgs84(); 49 | b.iter(|| { 50 | for (lat1, lon1, azi1, s12) in inputs.clone() { 51 | let (_lat2, _lon2, _azi2) = geod.direct(lat1, lon1, azi1, s12); 52 | } 53 | }) 54 | }); 55 | group.finish(); 56 | } 57 | 58 | { 59 | let mut group = c.benchmark_group("direct (rust impl)"); 60 | if mode == TEST_MODE_FULL { 61 | group.measurement_time(Duration::from_secs(35)); 62 | } 63 | group.bench_function(mode, |b| { 64 | let geod = geographiclib_rs::Geodesic::wgs84(); 65 | b.iter(|| { 66 | for (lat1, lon1, azi1, s12) in inputs.clone() { 67 | // Do work comparable to geographiclib c-wrapper's `geod.direct` method 68 | let (_lat2, _lon2, _azi2) = geod.direct(lat1, lon1, azi1, s12); 69 | } 70 | }) 71 | }); 72 | group.finish(); 73 | } 74 | } 75 | 76 | fn geodesic_inverse_benchmark(c: &mut Criterion) { 77 | let (mode, file_path) = test_input_path(); 78 | let file = File::open(file_path).unwrap(); 79 | let reader = BufReader::new(file); 80 | let inputs: Vec<(f64, f64, f64, f64)> = reader 81 | .lines() 82 | .map(|line| { 83 | let line = line.unwrap(); 84 | let fields: Vec = line.split(' ').map(|s| s.parse::().unwrap()).collect(); 85 | (fields[0], fields[1], fields[3], fields[4]) 86 | }) 87 | .collect(); 88 | 89 | { 90 | let mut group = c.benchmark_group("inverse (c wrapper)"); 91 | if mode == TEST_MODE_FULL { 92 | group.measurement_time(Duration::from_secs(50)); 93 | } 94 | group.bench_function(mode, |b| { 95 | let geod = geographiclib::Geodesic::wgs84(); 96 | b.iter(|| { 97 | for (lat1, lon1, lat2, lon2) in inputs.clone() { 98 | let (_s12, _azi1, _azi2, _a12) = geod.inverse(lat1, lon1, lat2, lon2); 99 | } 100 | }) 101 | }); 102 | group.finish(); 103 | } 104 | 105 | { 106 | let mut group = c.benchmark_group("inverse (rust impl)"); 107 | if mode == TEST_MODE_FULL { 108 | group.measurement_time(Duration::from_secs(70)); 109 | } 110 | group.bench_function(mode, |b| { 111 | let geod = geographiclib_rs::Geodesic::wgs84(); 112 | b.iter(|| { 113 | for (lat1, lon1, lat2, lon2) in inputs.clone() { 114 | // Do work comparable to geographiclib c-wrapper's `geod.inverse` method 115 | let (_s12, _azi1, _azi2, _a12) = geod.inverse(lat1, lon1, lat2, lon2); 116 | } 117 | }) 118 | }); 119 | group.finish(); 120 | } 121 | } 122 | 123 | criterion_group!( 124 | benches, 125 | geodesic_direct_benchmark, 126 | geodesic_inverse_benchmark 127 | ); 128 | criterion_main!(benches); 129 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "tests" 3 | ] 4 | -------------------------------------------------------------------------------- /examples/direct.rs: -------------------------------------------------------------------------------- 1 | use geographiclib_rs::{DirectGeodesic, Geodesic}; 2 | fn main() { 3 | let g = Geodesic::wgs84(); 4 | let jfk_lat = 40.64; 5 | let jfk_lon = -73.78; 6 | let northeast_azimuth = 45.0; 7 | 8 | let (lat, lon, az) = g.direct(jfk_lat, jfk_lon, northeast_azimuth, 10e6); 9 | 10 | use approx::assert_relative_eq; 11 | assert_relative_eq!(lat, 32.621100463725796); 12 | assert_relative_eq!(lon, 49.05248709295982, epsilon = 1e-13); 13 | assert_relative_eq!(az, 140.4059858768007); 14 | 15 | println!("lat: {lat}, lon: {lon}, az: {az}"); 16 | } 17 | -------------------------------------------------------------------------------- /script/download-test-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | REPO_ROOT=$(git rev-parse --show-toplevel) 6 | OUTPUT_DIR="${REPO_ROOT}/test_fixtures/test_data_unzipped" 7 | 8 | mkdir -p "${OUTPUT_DIR}" 9 | 10 | curl -L https://sourceforge.net/projects/geographiclib/files/testdata/GeodTest.dat.gz | gunzip > "${OUTPUT_DIR}/GeodTest.dat" 11 | curl -L https://sourceforge.net/projects/geographiclib/files/testdata/GeodTest-short.dat.gz | gunzip > "${OUTPUT_DIR}/GeodTest-short.dat" 12 | 13 | -------------------------------------------------------------------------------- /src/bin/geodsolve.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::{self, BufRead, BufReader}; 4 | 5 | use geographiclib_rs::{DirectGeodesic, Geodesic, InverseGeodesic}; 6 | use std::error::Error; 7 | 8 | struct Runner { 9 | geod: Geodesic, 10 | is_full_output: bool, 11 | is_inverse: bool, 12 | input_filename: Option, 13 | } 14 | 15 | fn main() -> Result<(), Box> { 16 | let geod = Geodesic::wgs84(); 17 | let mut is_full_output_arg: Option = None; 18 | let mut is_inverse_arg: Option = None; 19 | let mut input_file_arg: Option = None; 20 | let mut next_arg_is_inputfile = false; 21 | 22 | for argument in env::args() { 23 | if argument == "--input-file" { 24 | next_arg_is_inputfile = true; 25 | } else if next_arg_is_inputfile { 26 | next_arg_is_inputfile = false; 27 | input_file_arg = Some(argument); 28 | } else if argument == "-i" { 29 | is_inverse_arg = Some(true); 30 | } else if argument == "-f" { 31 | is_full_output_arg = Some(true); 32 | } 33 | } 34 | 35 | let is_full_output = is_full_output_arg.unwrap_or(false); 36 | let is_inverse = is_inverse_arg.unwrap_or(false); 37 | Runner::new(geod, is_full_output, is_inverse, input_file_arg).run() 38 | } 39 | 40 | impl Runner { 41 | pub fn new( 42 | geod: Geodesic, 43 | is_full_output: bool, 44 | is_inverse: bool, 45 | input_filename: Option, 46 | ) -> Self { 47 | Runner { 48 | geod, 49 | is_full_output, 50 | is_inverse, 51 | input_filename, 52 | } 53 | } 54 | 55 | pub fn run(&self) -> Result<(), Box> { 56 | if let Some(input_filename) = &self.input_filename { 57 | let file = File::open(input_filename)?; 58 | let reader = BufReader::new(file); 59 | for line in reader.lines() { 60 | let line = line.unwrap(); 61 | self.handle_line(line); 62 | } 63 | } else { 64 | for line in io::stdin().lock().lines() { 65 | let line = line.unwrap(); 66 | self.handle_line(line); 67 | } 68 | } 69 | Ok(()) 70 | } 71 | 72 | fn handle_line(&self, line: String) { 73 | let fields: Vec = line.split(' ').map(|s| s.parse::().unwrap()).collect(); 74 | let output_fields = if self.is_inverse { 75 | self.compute_inverse(&fields) 76 | } else { 77 | self.compute_direct(&fields) 78 | }; 79 | let output_strings: Vec = output_fields.iter().map(|f| f.to_string()).collect(); 80 | let output_line = output_strings.join(" "); 81 | println!("{}", output_line); 82 | } 83 | 84 | fn compute_direct(&self, fields: &[f64]) -> Vec { 85 | assert_eq!(4, fields.len()); 86 | let lat1 = fields[0]; 87 | let lon1 = fields[1]; 88 | let azi1 = fields[2]; 89 | let s12 = fields[3]; 90 | #[allow(non_snake_case)] 91 | let ( 92 | computed_lat2, 93 | computed_lon2, 94 | computed_azi2, 95 | computed_m12, 96 | _computed_M12, 97 | _computed_M21, 98 | _computed_S12, 99 | computed_a12, 100 | ) = self.geod.direct(lat1, lon1, azi1, s12); 101 | 102 | if self.is_full_output { 103 | // TODO - we're currently omitting several fields, and only outputting what's 104 | // necessary to pass the validation tool 105 | vec![ 106 | lat1, 107 | lon1, 108 | azi1, 109 | computed_lat2, 110 | computed_lon2, 111 | computed_azi2, 112 | s12, 113 | computed_a12, 114 | computed_m12, 115 | ] 116 | } else { 117 | vec![computed_lat2, computed_lon2, computed_azi2] 118 | } 119 | } 120 | 121 | fn compute_inverse(&self, fields: &[f64]) -> Vec { 122 | assert_eq!(4, fields.len()); 123 | let input_lat1 = fields[0]; 124 | let input_lon1 = fields[1]; 125 | let input_lat2 = fields[2]; 126 | let input_lon2 = fields[3]; 127 | 128 | #[allow(non_snake_case)] 129 | let (s12, azi1, azi2, m12, _M12, _M21, _S12, a12) = self 130 | .geod 131 | .inverse(input_lat1, input_lon1, input_lat2, input_lon2); 132 | 133 | if self.is_full_output { 134 | // TODO - we're currently omitting several fields, and only outputting what's 135 | // necessary to pass the validation tool 136 | vec![ 137 | input_lat1, input_lon1, azi1, input_lat2, input_lon2, azi2, s12, a12, m12, 138 | ] 139 | } else { 140 | vec![azi1, azi2, s12] 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/geodesic.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | #![allow(clippy::excessive_precision)] 3 | 4 | use crate::geodesic_capability as caps; 5 | use crate::geodesic_line; 6 | use crate::geomath; 7 | use std::sync; 8 | 9 | use std::f64::consts::{FRAC_1_SQRT_2, PI}; 10 | 11 | pub const WGS84_A: f64 = 6378137.0; 12 | // Evaluating this as 1000000000.0 / (298257223563f64) reduces the 13 | // round-off error by about 10%. However, expressing the flattening as 14 | // 1/298.257223563 is well ingrained. 15 | pub const WGS84_F: f64 = 1.0 / ((298257223563f64) / 1000000000.0); 16 | 17 | #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] 18 | pub struct Geodesic { 19 | pub a: f64, 20 | pub f: f64, 21 | pub _f1: f64, 22 | pub _e2: f64, 23 | pub _ep2: f64, 24 | _n: f64, 25 | pub _b: f64, 26 | pub _c2: f64, 27 | _etol2: f64, 28 | _A3x: [f64; GEODESIC_ORDER], 29 | _C3x: [f64; _nC3x_], 30 | _C4x: [f64; _nC4x_], 31 | 32 | pub GEODESIC_ORDER: usize, 33 | _nC3x_: usize, 34 | _nC4x_: usize, 35 | maxit1_: u64, 36 | maxit2_: u64, 37 | 38 | pub tiny_: f64, 39 | tol0_: f64, 40 | tol1_: f64, 41 | _tol2_: f64, 42 | tolb_: f64, 43 | xthresh_: f64, 44 | } 45 | 46 | static WGS84_GEOD: sync::OnceLock = sync::OnceLock::new(); 47 | 48 | impl Geodesic { 49 | pub fn wgs84() -> Self { 50 | *WGS84_GEOD.get_or_init(|| Geodesic::new(WGS84_A, WGS84_F)) 51 | } 52 | 53 | pub fn equatorial_radius(&self) -> f64 { 54 | self.a 55 | } 56 | 57 | pub fn flattening(&self) -> f64 { 58 | self.f 59 | } 60 | } 61 | 62 | const COEFF_A3: [f64; 18] = [ 63 | -3.0, 128.0, -2.0, -3.0, 64.0, -1.0, -3.0, -1.0, 16.0, 3.0, -1.0, -2.0, 8.0, 1.0, -1.0, 2.0, 64 | 1.0, 1.0, 65 | ]; 66 | 67 | const COEFF_C3: [f64; 45] = [ 68 | 3.0, 128.0, 2.0, 5.0, 128.0, -1.0, 3.0, 3.0, 64.0, -1.0, 0.0, 1.0, 8.0, -1.0, 1.0, 4.0, 5.0, 69 | 256.0, 1.0, 3.0, 128.0, -3.0, -2.0, 3.0, 64.0, 1.0, -3.0, 2.0, 32.0, 7.0, 512.0, -10.0, 9.0, 70 | 384.0, 5.0, -9.0, 5.0, 192.0, 7.0, 512.0, -14.0, 7.0, 512.0, 21.0, 2560.0, 71 | ]; 72 | 73 | const COEFF_C4: [f64; 77] = [ 74 | 97.0, 15015.0, 1088.0, 156.0, 45045.0, -224.0, -4784.0, 1573.0, 45045.0, -10656.0, 14144.0, 75 | -4576.0, -858.0, 45045.0, 64.0, 624.0, -4576.0, 6864.0, -3003.0, 15015.0, 100.0, 208.0, 572.0, 76 | 3432.0, -12012.0, 30030.0, 45045.0, 1.0, 9009.0, -2944.0, 468.0, 135135.0, 5792.0, 1040.0, 77 | -1287.0, 135135.0, 5952.0, -11648.0, 9152.0, -2574.0, 135135.0, -64.0, -624.0, 4576.0, -6864.0, 78 | 3003.0, 135135.0, 8.0, 10725.0, 1856.0, -936.0, 225225.0, -8448.0, 4992.0, -1144.0, 225225.0, 79 | -1440.0, 4160.0, -4576.0, 1716.0, 225225.0, -136.0, 63063.0, 1024.0, -208.0, 105105.0, 3584.0, 80 | -3328.0, 1144.0, 315315.0, -128.0, 135135.0, -2560.0, 832.0, 405405.0, 128.0, 99099.0, 81 | ]; 82 | 83 | pub const GEODESIC_ORDER: usize = 6; 84 | #[allow(non_upper_case_globals)] 85 | const _nC3x_: usize = 15; 86 | #[allow(non_upper_case_globals)] 87 | const _nC4x_: usize = 21; 88 | 89 | impl Geodesic { 90 | pub fn new(a: f64, f: f64) -> Self { 91 | let maxit1_ = 20; 92 | let maxit2_ = maxit1_ + geomath::DIGITS + 10; 93 | let tiny_ = geomath::get_min_val().sqrt(); 94 | let tol0_ = geomath::get_epsilon(); 95 | let tol1_ = 200.0 * tol0_; 96 | let _tol2_ = tol0_.sqrt(); 97 | let tolb_ = tol0_ * _tol2_; 98 | let xthresh_ = 1000.0 * _tol2_; 99 | 100 | let _f1 = 1.0 - f; 101 | let _e2 = f * (2.0 - f); 102 | let _ep2 = _e2 / geomath::sq(_f1); 103 | let _n = f / (2.0 - f); 104 | let _b = a * _f1; 105 | let _c2 = (geomath::sq(a) 106 | + geomath::sq(_b) 107 | * (if _e2 == 0.0 { 108 | 1.0 109 | } else { 110 | geomath::eatanhe(1.0, (if f < 0.0 { -1.0 } else { 1.0 }) * _e2.abs().sqrt()) 111 | / _e2 112 | })) 113 | / 2.0; 114 | let _etol2 = 0.1 * _tol2_ / (f.abs().max(0.001) * (1.0 - f / 2.0).min(1.0) / 2.0).sqrt(); 115 | 116 | let mut _A3x: [f64; GEODESIC_ORDER] = [0.0; GEODESIC_ORDER]; 117 | let mut _C3x: [f64; _nC3x_] = [0.0; _nC3x_]; 118 | let mut _C4x: [f64; _nC4x_] = [0.0; _nC4x_]; 119 | 120 | // Call a3coeff 121 | let mut o: usize = 0; 122 | for (k, j) in (0..GEODESIC_ORDER).rev().enumerate() { 123 | let m = j.min(GEODESIC_ORDER - j - 1); 124 | _A3x[k] = geomath::polyval(m, &COEFF_A3[o..], _n) / COEFF_A3[o + m + 1]; 125 | o += m + 2; 126 | } 127 | 128 | // c3coeff 129 | let mut o = 0; 130 | let mut k = 0; 131 | for l in 1..GEODESIC_ORDER { 132 | for j in (l..GEODESIC_ORDER).rev() { 133 | let m = j.min(GEODESIC_ORDER - j - 1); 134 | _C3x[k] = geomath::polyval(m, &COEFF_C3[o..], _n) / COEFF_C3[o + m + 1]; 135 | k += 1; 136 | o += m + 2; 137 | } 138 | } 139 | 140 | // c4coeff 141 | let mut o = 0; 142 | let mut k = 0; 143 | for l in 0..GEODESIC_ORDER { 144 | for j in (l..GEODESIC_ORDER).rev() { 145 | let m = GEODESIC_ORDER - j - 1; 146 | _C4x[k] = geomath::polyval(m, &COEFF_C4[o..], _n) / COEFF_C4[o + m + 1]; 147 | k += 1; 148 | o += m + 2; 149 | } 150 | } 151 | 152 | Geodesic { 153 | a, 154 | f, 155 | _f1, 156 | _e2, 157 | _ep2, 158 | _n, 159 | _b, 160 | _c2, 161 | _etol2, 162 | _A3x, 163 | _C3x, 164 | _C4x, 165 | 166 | GEODESIC_ORDER, 167 | _nC3x_, 168 | _nC4x_, 169 | maxit1_, 170 | maxit2_, 171 | 172 | tiny_, 173 | tol0_, 174 | tol1_, 175 | _tol2_, 176 | tolb_, 177 | xthresh_, 178 | } 179 | } 180 | 181 | pub fn _A3f(&self, eps: f64) -> f64 { 182 | geomath::polyval(self.GEODESIC_ORDER - 1, &self._A3x, eps) 183 | } 184 | 185 | pub fn _C3f(&self, eps: f64, c: &mut [f64]) { 186 | let mut mult = 1.0; 187 | let mut o = 0; 188 | // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)` 189 | // but benching (rust-1.75) shows that it would be slower. 190 | #[allow(clippy::needless_range_loop)] 191 | for l in 1..GEODESIC_ORDER { 192 | let m = GEODESIC_ORDER - l - 1; 193 | mult *= eps; 194 | c[l] = mult * geomath::polyval(m, &self._C3x[o..], eps); 195 | o += m + 1; 196 | } 197 | } 198 | 199 | pub fn _C4f(&self, eps: f64, c: &mut [f64]) { 200 | let mut mult = 1.0; 201 | let mut o = 0; 202 | // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)` 203 | // but benching (rust-1.75) shows that it would be slower. 204 | #[allow(clippy::needless_range_loop)] 205 | for l in 0..GEODESIC_ORDER { 206 | let m = GEODESIC_ORDER - l - 1; 207 | c[l] = mult * geomath::polyval(m, &self._C4x[o..], eps); 208 | o += m + 1; 209 | mult *= eps; 210 | } 211 | } 212 | 213 | #[allow(clippy::too_many_arguments)] 214 | pub fn _Lengths( 215 | &self, 216 | eps: f64, 217 | sig12: f64, 218 | ssig1: f64, 219 | csig1: f64, 220 | dn1: f64, 221 | ssig2: f64, 222 | csig2: f64, 223 | dn2: f64, 224 | cbet1: f64, 225 | cbet2: f64, 226 | outmask: u64, 227 | C1a: &mut [f64], 228 | C2a: &mut [f64], 229 | ) -> (f64, f64, f64, f64, f64) { 230 | let outmask = outmask & caps::OUT_MASK; 231 | let mut s12b = f64::NAN; 232 | let mut m12b = f64::NAN; 233 | let mut m0 = f64::NAN; 234 | let mut M12 = f64::NAN; 235 | let mut M21 = f64::NAN; 236 | 237 | let mut A1 = 0.0; 238 | let mut A2 = 0.0; 239 | let mut m0x = 0.0; 240 | let mut J12 = 0.0; 241 | 242 | if outmask & (caps::DISTANCE | caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 { 243 | A1 = geomath::_A1m1f(eps, self.GEODESIC_ORDER); 244 | geomath::_C1f(eps, C1a, self.GEODESIC_ORDER); 245 | if outmask & (caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 { 246 | A2 = geomath::_A2m1f(eps, self.GEODESIC_ORDER); 247 | geomath::_C2f(eps, C2a, self.GEODESIC_ORDER); 248 | m0x = A1 - A2; 249 | A2 += 1.0; 250 | } 251 | A1 += 1.0; 252 | } 253 | if outmask & caps::DISTANCE != 0 { 254 | let B1 = geomath::sin_cos_series(true, ssig2, csig2, C1a) 255 | - geomath::sin_cos_series(true, ssig1, csig1, C1a); 256 | s12b = A1 * (sig12 + B1); 257 | if outmask & (caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 { 258 | let B2 = geomath::sin_cos_series(true, ssig2, csig2, C2a) 259 | - geomath::sin_cos_series(true, ssig1, csig1, C2a); 260 | J12 = m0x * sig12 + (A1 * B1 - A2 * B2); 261 | } 262 | } else if outmask & (caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 { 263 | for l in 1..=self.GEODESIC_ORDER { 264 | C2a[l] = A1 * C1a[l] - A2 * C2a[l]; 265 | } 266 | J12 = m0x * sig12 267 | + (geomath::sin_cos_series(true, ssig2, csig2, C2a) 268 | - geomath::sin_cos_series(true, ssig1, csig1, C2a)); 269 | } 270 | if outmask & caps::REDUCEDLENGTH != 0 { 271 | m0 = m0x; 272 | // J12 is wrong 273 | m12b = dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) - csig1 * csig2 * J12; 274 | } 275 | if outmask & caps::GEODESICSCALE != 0 { 276 | let csig12 = csig1 * csig2 + ssig1 * ssig2; 277 | let t = self._ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2); 278 | M12 = csig12 + (t * ssig2 - csig2 * J12) * ssig1 / dn1; 279 | M21 = csig12 - (t * ssig1 - csig1 * J12) * ssig2 / dn2; 280 | } 281 | (s12b, m12b, m0, M12, M21) 282 | } 283 | 284 | #[allow(clippy::too_many_arguments)] 285 | pub fn _InverseStart( 286 | &self, 287 | sbet1: f64, 288 | cbet1: f64, 289 | dn1: f64, 290 | sbet2: f64, 291 | cbet2: f64, 292 | dn2: f64, 293 | lam12: f64, 294 | slam12: f64, 295 | clam12: f64, 296 | C1a: &mut [f64], 297 | C2a: &mut [f64], 298 | ) -> (f64, f64, f64, f64, f64, f64) { 299 | let mut sig12 = -1.0; 300 | let mut salp2 = f64::NAN; 301 | let mut calp2 = f64::NAN; 302 | let mut dnm = f64::NAN; 303 | 304 | let mut somg12: f64; 305 | let mut comg12: f64; 306 | 307 | let sbet12 = sbet2 * cbet1 - cbet2 * sbet1; 308 | let cbet12 = cbet2 * cbet1 + sbet2 * sbet1; 309 | 310 | let mut sbet12a = sbet2 * cbet1; 311 | sbet12a += cbet2 * sbet1; 312 | 313 | let shortline = cbet12 >= 0.0 && sbet12 < 0.5 && cbet2 * lam12 < 0.5; 314 | if shortline { 315 | let mut sbetm2 = geomath::sq(sbet1 + sbet2); 316 | sbetm2 /= sbetm2 + geomath::sq(cbet1 + cbet2); 317 | dnm = (1.0 + self._ep2 * sbetm2).sqrt(); 318 | let omg12 = lam12 / (self._f1 * dnm); 319 | somg12 = omg12.sin(); 320 | comg12 = omg12.cos(); 321 | } else { 322 | somg12 = slam12; 323 | comg12 = clam12; 324 | } 325 | 326 | let mut salp1 = cbet2 * somg12; 327 | 328 | let mut calp1 = if comg12 >= 0.0 { 329 | sbet12 + cbet2 * sbet1 * geomath::sq(somg12) / (1.0 + comg12) 330 | } else { 331 | sbet12a - cbet2 * sbet1 * geomath::sq(somg12) / (1.0 - comg12) 332 | }; 333 | 334 | let ssig12 = salp1.hypot(calp1); 335 | let csig12 = sbet1 * sbet2 + cbet1 * cbet2 * comg12; 336 | 337 | if shortline && ssig12 < self._etol2 { 338 | salp2 = cbet1 * somg12; 339 | calp2 = sbet12 340 | - cbet1 341 | * sbet2 342 | * (if comg12 >= 0.0 { 343 | geomath::sq(somg12) / (1.0 + comg12) 344 | } else { 345 | 1.0 - comg12 346 | }); 347 | geomath::norm(&mut salp2, &mut calp2); 348 | sig12 = ssig12.atan2(csig12); 349 | } else if self._n.abs() > 0.1 350 | || csig12 >= 0.0 351 | || ssig12 >= 6.0 * self._n.abs() * PI * geomath::sq(cbet1) 352 | { 353 | } else { 354 | let x: f64; 355 | let y: f64; 356 | let betscale: f64; 357 | let lamscale: f64; 358 | let lam12x = (-slam12).atan2(-clam12); 359 | if self.f >= 0.0 { 360 | let k2 = geomath::sq(sbet1) * self._ep2; 361 | let eps = k2 / (2.0 * (1.0 + (1.0 + k2).sqrt()) + k2); 362 | lamscale = self.f * cbet1 * self._A3f(eps) * PI; 363 | betscale = lamscale * cbet1; 364 | x = lam12x / lamscale; 365 | y = sbet12a / betscale; 366 | } else { 367 | let cbet12a = cbet2 * cbet1 - sbet2 * sbet1; 368 | let bet12a = sbet12a.atan2(cbet12a); 369 | let (_, m12b, m0, _, _) = self._Lengths( 370 | self._n, 371 | PI + bet12a, 372 | sbet1, 373 | -cbet1, 374 | dn1, 375 | sbet2, 376 | cbet2, 377 | dn2, 378 | cbet1, 379 | cbet2, 380 | caps::REDUCEDLENGTH, 381 | C1a, 382 | C2a, 383 | ); 384 | x = -1.0 + m12b / (cbet1 * cbet2 * m0 * PI); 385 | betscale = if x < -0.01 { 386 | sbet12a / x 387 | } else { 388 | -self.f * geomath::sq(cbet1) * PI 389 | }; 390 | lamscale = betscale / cbet1; 391 | y = lam12x / lamscale; 392 | } 393 | if y > -self.tol1_ && x > -1.0 - self.xthresh_ { 394 | if self.f >= 0.0 { 395 | salp1 = (-x).min(1.0); 396 | calp1 = -(1.0 - geomath::sq(salp1)).sqrt() 397 | } else { 398 | calp1 = x.max(if x > -self.tol1_ { 0.0 } else { -1.0 }); 399 | salp1 = (1.0 - geomath::sq(calp1)).sqrt(); 400 | } 401 | } else { 402 | let k = geomath::astroid(x, y); 403 | let omg12a = lamscale 404 | * if self.f >= 0.0 { 405 | -x * k / (1.0 + k) 406 | } else { 407 | -y * (1.0 + k) / k 408 | }; 409 | somg12 = omg12a.sin(); 410 | comg12 = -(omg12a.cos()); 411 | salp1 = cbet2 * somg12; 412 | calp1 = sbet12a - cbet2 * sbet1 * geomath::sq(somg12) / (1.0 - comg12); 413 | } 414 | } 415 | 416 | if salp1 > 0.0 || salp1.is_nan() { 417 | geomath::norm(&mut salp1, &mut calp1); 418 | } else { 419 | salp1 = 1.0; 420 | calp1 = 0.0; 421 | }; 422 | (sig12, salp1, calp1, salp2, calp2, dnm) 423 | } 424 | 425 | #[allow(clippy::too_many_arguments)] 426 | pub fn _Lambda12( 427 | &self, 428 | sbet1: f64, 429 | cbet1: f64, 430 | dn1: f64, 431 | sbet2: f64, 432 | cbet2: f64, 433 | dn2: f64, 434 | salp1: f64, 435 | mut calp1: f64, 436 | slam120: f64, 437 | clam120: f64, 438 | diffp: bool, 439 | C1a: &mut [f64], 440 | C2a: &mut [f64], 441 | C3a: &mut [f64], 442 | ) -> (f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64) { 443 | if sbet1 == 0.0 && calp1 == 0.0 { 444 | calp1 = -self.tiny_; 445 | } 446 | let salp0 = salp1 * cbet1; 447 | let calp0 = calp1.hypot(salp1 * sbet1); 448 | 449 | let mut ssig1 = sbet1; 450 | let somg1 = salp0 * sbet1; 451 | let mut csig1 = calp1 * cbet1; 452 | let comg1 = calp1 * cbet1; 453 | geomath::norm(&mut ssig1, &mut csig1); 454 | 455 | let salp2 = if cbet2 != cbet1 { salp0 / cbet2 } else { salp1 }; 456 | let calp2 = if cbet2 != cbet1 || sbet2.abs() != -sbet1 { 457 | (geomath::sq(calp1 * cbet1) 458 | + if cbet1 < -sbet1 { 459 | (cbet2 - cbet1) * (cbet1 + cbet2) 460 | } else { 461 | (sbet1 - sbet2) * (sbet1 + sbet2) 462 | }) 463 | .sqrt() 464 | / cbet2 465 | } else { 466 | calp1.abs() 467 | }; 468 | let mut ssig2 = sbet2; 469 | let somg2 = salp0 * sbet2; 470 | let mut csig2 = calp2 * cbet2; 471 | let comg2 = calp2 * cbet2; 472 | geomath::norm(&mut ssig2, &mut csig2); 473 | 474 | let sig12 = ((csig1 * ssig2 - ssig1 * csig2).max(0.0)).atan2(csig1 * csig2 + ssig1 * ssig2); 475 | let somg12 = (comg1 * somg2 - somg1 * comg2).max(0.0); 476 | let comg12 = comg1 * comg2 + somg1 * somg2; 477 | let eta = (somg12 * clam120 - comg12 * slam120).atan2(comg12 * clam120 + somg12 * slam120); 478 | 479 | let k2 = geomath::sq(calp0) * self._ep2; 480 | let eps = k2 / (2.0 * (1.0 + (1.0 + k2).sqrt()) + k2); 481 | self._C3f(eps, C3a); 482 | let B312 = geomath::sin_cos_series(true, ssig2, csig2, C3a) 483 | - geomath::sin_cos_series(true, ssig1, csig1, C3a); 484 | let domg12 = -self.f * self._A3f(eps) * salp0 * (sig12 + B312); 485 | let lam12 = eta + domg12; 486 | 487 | let mut dlam12: f64; 488 | if diffp { 489 | if calp2 == 0.0 { 490 | dlam12 = -2.0 * self._f1 * dn1 / sbet1; 491 | } else { 492 | let res = self._Lengths( 493 | eps, 494 | sig12, 495 | ssig1, 496 | csig1, 497 | dn1, 498 | ssig2, 499 | csig2, 500 | dn2, 501 | cbet1, 502 | cbet2, 503 | caps::REDUCEDLENGTH, 504 | C1a, 505 | C2a, 506 | ); 507 | dlam12 = res.1; 508 | dlam12 *= self._f1 / (calp2 * cbet2); 509 | } 510 | } else { 511 | dlam12 = f64::NAN; 512 | } 513 | ( 514 | lam12, salp2, calp2, sig12, ssig1, csig1, ssig2, csig2, eps, domg12, dlam12, 515 | ) 516 | } 517 | 518 | // returns (a12, s12, azi1, azi2, m12, M12, M21, S12) 519 | pub fn _gen_inverse_azi( 520 | &self, 521 | lat1: f64, 522 | lon1: f64, 523 | lat2: f64, 524 | lon2: f64, 525 | outmask: u64, 526 | ) -> (f64, f64, f64, f64, f64, f64, f64, f64) { 527 | let mut azi1 = f64::NAN; 528 | let mut azi2 = f64::NAN; 529 | let outmask = outmask & caps::OUT_MASK; 530 | 531 | let (a12, s12, salp1, calp1, salp2, calp2, m12, M12, M21, S12) = 532 | self._gen_inverse(lat1, lon1, lat2, lon2, outmask); 533 | if outmask & caps::AZIMUTH != 0 { 534 | azi1 = geomath::atan2d(salp1, calp1); 535 | azi2 = geomath::atan2d(salp2, calp2); 536 | } 537 | (a12, s12, azi1, azi2, m12, M12, M21, S12) 538 | } 539 | 540 | // returns (a12, s12, salp1, calp1, salp2, calp2, m12, M12, M21, S12) 541 | pub fn _gen_inverse( 542 | &self, 543 | lat1: f64, 544 | lon1: f64, 545 | lat2: f64, 546 | lon2: f64, 547 | outmask: u64, 548 | ) -> (f64, f64, f64, f64, f64, f64, f64, f64, f64, f64) { 549 | let mut lat1 = lat1; 550 | let mut lat2 = lat2; 551 | let mut a12 = f64::NAN; 552 | let mut s12 = f64::NAN; 553 | let mut m12 = f64::NAN; 554 | let mut M12 = f64::NAN; 555 | let mut M21 = f64::NAN; 556 | let mut S12 = f64::NAN; 557 | let outmask = outmask & caps::OUT_MASK; 558 | 559 | let (mut lon12, mut lon12s) = geomath::ang_diff(lon1, lon2); 560 | let mut lonsign = if lon12 >= 0.0 { 1.0 } else { -1.0 }; 561 | 562 | lon12 = lonsign * geomath::ang_round(lon12); 563 | lon12s = geomath::ang_round((180.0 - lon12) - lonsign * lon12s); 564 | let lam12 = lon12.to_radians(); 565 | let slam12: f64; 566 | let mut clam12: f64; 567 | if lon12 > 90.0 { 568 | let res = geomath::sincosd(lon12s); 569 | slam12 = res.0; 570 | clam12 = res.1; 571 | clam12 = -clam12; 572 | } else { 573 | let res = geomath::sincosd(lon12); 574 | slam12 = res.0; 575 | clam12 = res.1; 576 | }; 577 | lat1 = geomath::ang_round(geomath::lat_fix(lat1)); 578 | lat2 = geomath::ang_round(geomath::lat_fix(lat2)); 579 | 580 | let swapp = if lat1.abs() < lat2.abs() { -1.0 } else { 1.0 }; 581 | if swapp < 0.0 { 582 | lonsign *= -1.0; 583 | std::mem::swap(&mut lat2, &mut lat1); 584 | } 585 | let latsign = if lat1 < 0.0 { 1.0 } else { -1.0 }; 586 | lat1 *= latsign; 587 | lat2 *= latsign; 588 | 589 | let (mut sbet1, mut cbet1) = geomath::sincosd(lat1); 590 | sbet1 *= self._f1; 591 | 592 | geomath::norm(&mut sbet1, &mut cbet1); 593 | cbet1 = cbet1.max(self.tiny_); 594 | 595 | let (mut sbet2, mut cbet2) = geomath::sincosd(lat2); 596 | sbet2 *= self._f1; 597 | 598 | geomath::norm(&mut sbet2, &mut cbet2); 599 | cbet2 = cbet2.max(self.tiny_); 600 | 601 | if cbet1 < -sbet1 { 602 | if cbet2 == cbet1 { 603 | sbet2 = if sbet2 < 0.0 { sbet1 } else { -sbet1 }; 604 | } 605 | } else if sbet2.abs() == -sbet1 { 606 | cbet2 = cbet1; 607 | } 608 | 609 | let dn1 = (1.0 + self._ep2 * geomath::sq(sbet1)).sqrt(); 610 | let dn2 = (1.0 + self._ep2 * geomath::sq(sbet2)).sqrt(); 611 | 612 | const CARR_SIZE: usize = GEODESIC_ORDER + 1; 613 | let mut C1a: [f64; CARR_SIZE] = [0.0; CARR_SIZE]; 614 | let mut C2a: [f64; CARR_SIZE] = [0.0; CARR_SIZE]; 615 | let mut C3a: [f64; GEODESIC_ORDER] = [0.0; GEODESIC_ORDER]; 616 | 617 | let mut meridian = lat1 == -90.0 || slam12 == 0.0; 618 | let mut calp1 = 0.0; 619 | let mut salp1 = 0.0; 620 | let mut calp2 = 0.0; 621 | let mut salp2 = 0.0; 622 | let mut ssig1 = 0.0; 623 | let mut csig1 = 0.0; 624 | let mut ssig2 = 0.0; 625 | let mut csig2 = 0.0; 626 | let mut sig12: f64; 627 | let mut s12x = 0.0; 628 | let mut m12x = 0.0; 629 | 630 | if meridian { 631 | calp1 = clam12; 632 | salp1 = slam12; 633 | calp2 = 1.0; 634 | salp2 = 0.0; 635 | 636 | ssig1 = sbet1; 637 | csig1 = calp1 * cbet1; 638 | ssig2 = sbet2; 639 | csig2 = calp2 * cbet2; 640 | 641 | sig12 = ((csig1 * ssig2 - ssig1 * csig2).max(0.0)).atan2(csig1 * csig2 + ssig1 * ssig2); 642 | let res = self._Lengths( 643 | self._n, 644 | sig12, 645 | ssig1, 646 | csig1, 647 | dn1, 648 | ssig2, 649 | csig2, 650 | dn2, 651 | cbet1, 652 | cbet2, 653 | outmask | caps::DISTANCE | caps::REDUCEDLENGTH, 654 | &mut C1a, 655 | &mut C2a, 656 | ); 657 | s12x = res.0; 658 | m12x = res.1; 659 | M12 = res.3; 660 | M21 = res.4; 661 | 662 | if sig12 < 1.0 || m12x >= 0.0 { 663 | if sig12 < 3.0 * self.tiny_ { 664 | sig12 = 0.0; 665 | m12x = 0.0; 666 | s12x = 0.0; 667 | } 668 | m12x *= self._b; 669 | s12x *= self._b; 670 | a12 = sig12.to_degrees(); 671 | } else { 672 | meridian = false; 673 | } 674 | } 675 | 676 | let mut somg12 = 2.0; 677 | let mut comg12 = 0.0; 678 | let mut omg12 = 0.0; 679 | let dnm: f64; 680 | let mut eps = 0.0; 681 | if !meridian && sbet1 == 0.0 && (self.f <= 0.0 || lon12s >= self.f * 180.0) { 682 | calp1 = 0.0; 683 | calp2 = 0.0; 684 | salp1 = 1.0; 685 | salp2 = 1.0; 686 | 687 | s12x = self.a * lam12; 688 | sig12 = lam12 / self._f1; 689 | omg12 = lam12 / self._f1; 690 | m12x = self._b * sig12.sin(); 691 | if outmask & caps::GEODESICSCALE != 0 { 692 | M12 = sig12.cos(); 693 | M21 = sig12.cos(); 694 | } 695 | a12 = lon12 / self._f1; 696 | } else if !meridian { 697 | let res = self._InverseStart( 698 | sbet1, cbet1, dn1, sbet2, cbet2, dn2, lam12, slam12, clam12, &mut C1a, &mut C2a, 699 | ); 700 | sig12 = res.0; 701 | salp1 = res.1; 702 | calp1 = res.2; 703 | salp2 = res.3; 704 | calp2 = res.4; 705 | dnm = res.5; 706 | 707 | if sig12 >= 0.0 { 708 | s12x = sig12 * self._b * dnm; 709 | m12x = geomath::sq(dnm) * self._b * (sig12 / dnm).sin(); 710 | if outmask & caps::GEODESICSCALE != 0 { 711 | M12 = (sig12 / dnm).cos(); 712 | M21 = (sig12 / dnm).cos(); 713 | } 714 | a12 = sig12.to_degrees(); 715 | omg12 = lam12 / (self._f1 * dnm); 716 | } else { 717 | let mut tripn = false; 718 | let mut tripb = false; 719 | let mut salp1a = self.tiny_; 720 | let mut calp1a = 1.0; 721 | let mut salp1b = self.tiny_; 722 | let mut calp1b = -1.0; 723 | let mut domg12 = 0.0; 724 | for numit in 0..self.maxit2_ { 725 | let res = self._Lambda12( 726 | sbet1, 727 | cbet1, 728 | dn1, 729 | sbet2, 730 | cbet2, 731 | dn2, 732 | salp1, 733 | calp1, 734 | slam12, 735 | clam12, 736 | numit < self.maxit1_, 737 | &mut C1a, 738 | &mut C2a, 739 | &mut C3a, 740 | ); 741 | let v = res.0; 742 | salp2 = res.1; 743 | calp2 = res.2; 744 | sig12 = res.3; 745 | ssig1 = res.4; 746 | csig1 = res.5; 747 | ssig2 = res.6; 748 | csig2 = res.7; 749 | eps = res.8; 750 | domg12 = res.9; 751 | let dv = res.10; 752 | 753 | if tripb 754 | || v.abs() < if tripn { 8.0 } else { 1.0 } * self.tol0_ 755 | || v.abs().is_nan() 756 | { 757 | break; 758 | }; 759 | if v > 0.0 && (numit > self.maxit1_ || calp1 / salp1 > calp1b / salp1b) { 760 | salp1b = salp1; 761 | calp1b = calp1; 762 | } else if v < 0.0 && (numit > self.maxit1_ || calp1 / salp1 < calp1a / salp1a) { 763 | salp1a = salp1; 764 | calp1a = calp1; 765 | } 766 | if numit < self.maxit1_ && dv > 0.0 { 767 | let dalp1 = -v / dv; 768 | let sdalp1 = dalp1.sin(); 769 | let cdalp1 = dalp1.cos(); 770 | let nsalp1 = salp1 * cdalp1 + calp1 * sdalp1; 771 | if nsalp1 > 0.0 && dalp1.abs() < PI { 772 | calp1 = calp1 * cdalp1 - salp1 * sdalp1; 773 | salp1 = nsalp1; 774 | geomath::norm(&mut salp1, &mut calp1); 775 | tripn = v.abs() <= 16.0 * self.tol0_; 776 | continue; 777 | } 778 | } 779 | 780 | salp1 = (salp1a + salp1b) / 2.0; 781 | calp1 = (calp1a + calp1b) / 2.0; 782 | geomath::norm(&mut salp1, &mut calp1); 783 | tripn = false; 784 | tripb = (salp1a - salp1).abs() + (calp1a - calp1) < self.tolb_ 785 | || (salp1 - salp1b).abs() + (calp1 - calp1b) < self.tolb_; 786 | } 787 | let lengthmask = outmask 788 | | if outmask & (caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 { 789 | caps::DISTANCE 790 | } else { 791 | caps::EMPTY 792 | }; 793 | let res = self._Lengths( 794 | eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, lengthmask, 795 | &mut C1a, &mut C2a, 796 | ); 797 | s12x = res.0; 798 | m12x = res.1; 799 | M12 = res.3; 800 | M21 = res.4; 801 | 802 | m12x *= self._b; 803 | s12x *= self._b; 804 | a12 = sig12.to_degrees(); 805 | if outmask & caps::AREA != 0 { 806 | let sdomg12 = domg12.sin(); 807 | let cdomg12 = domg12.cos(); 808 | somg12 = slam12 * cdomg12 - clam12 * sdomg12; 809 | comg12 = clam12 * cdomg12 + slam12 * sdomg12; 810 | } 811 | } 812 | } 813 | if outmask & caps::DISTANCE != 0 { 814 | s12 = 0.0 + s12x; 815 | } 816 | if outmask & caps::REDUCEDLENGTH != 0 { 817 | m12 = 0.0 + m12x; 818 | } 819 | if outmask & caps::AREA != 0 { 820 | let salp0 = salp1 * cbet1; 821 | let calp0 = calp1.hypot(salp1 * sbet1); 822 | if calp0 != 0.0 && salp0 != 0.0 { 823 | ssig1 = sbet1; 824 | csig1 = calp1 * cbet1; 825 | ssig2 = sbet2; 826 | csig2 = calp2 * cbet2; 827 | let k2 = geomath::sq(calp0) * self._ep2; 828 | eps = k2 / (2.0 * (1.0 + (1.0 + k2).sqrt()) + k2); 829 | let A4 = geomath::sq(self.a) * calp0 * salp0 * self._e2; 830 | geomath::norm(&mut ssig1, &mut csig1); 831 | geomath::norm(&mut ssig2, &mut csig2); 832 | let mut C4a: [f64; GEODESIC_ORDER] = [0.0; GEODESIC_ORDER]; 833 | self._C4f(eps, &mut C4a); 834 | let B41 = geomath::sin_cos_series(false, ssig1, csig1, &C4a); 835 | let B42 = geomath::sin_cos_series(false, ssig2, csig2, &C4a); 836 | S12 = A4 * (B42 - B41); 837 | } else { 838 | S12 = 0.0; 839 | } 840 | 841 | if !meridian && somg12 > 1.0 { 842 | somg12 = omg12.sin(); 843 | comg12 = omg12.cos(); 844 | } 845 | 846 | // We're diverging from Karney's implementation here 847 | // which uses the hardcoded constant: -0.7071 for FRAC_1_SQRT_2 848 | let alp12: f64; 849 | if !meridian && comg12 > -FRAC_1_SQRT_2 && sbet2 - sbet1 < 1.75 { 850 | let domg12 = 1.0 + comg12; 851 | let dbet1 = 1.0 + cbet1; 852 | let dbet2 = 1.0 + cbet2; 853 | alp12 = 2.0 854 | * (somg12 * (sbet1 * dbet2 + sbet2 * dbet1)) 855 | .atan2(domg12 * (sbet1 * sbet2 + dbet1 * dbet2)); 856 | } else { 857 | let mut salp12 = salp2 * calp1 - calp2 * salp1; 858 | let mut calp12 = calp2 * calp1 + salp2 * salp1; 859 | 860 | if salp12 == 0.0 && calp12 < 0.0 { 861 | salp12 = self.tiny_ * calp1; 862 | calp12 = -1.0; 863 | } 864 | alp12 = salp12.atan2(calp12); 865 | } 866 | S12 += self._c2 * alp12; 867 | S12 *= swapp * lonsign * latsign; 868 | S12 += 0.0; 869 | } 870 | 871 | if swapp < 0.0 { 872 | std::mem::swap(&mut salp2, &mut salp1); 873 | 874 | std::mem::swap(&mut calp2, &mut calp1); 875 | 876 | if outmask & caps::GEODESICSCALE != 0 { 877 | std::mem::swap(&mut M21, &mut M12); 878 | } 879 | } 880 | salp1 *= swapp * lonsign; 881 | calp1 *= swapp * latsign; 882 | salp2 *= swapp * lonsign; 883 | calp2 *= swapp * latsign; 884 | (a12, s12, salp1, calp1, salp2, calp2, m12, M12, M21, S12) 885 | } 886 | 887 | /// returns (a12, lat2, lon2, azi2, s12, m12, M12, M21, S12) 888 | pub fn _gen_direct( 889 | &self, 890 | lat1: f64, 891 | lon1: f64, 892 | azi1: f64, 893 | arcmode: bool, 894 | s12_a12: f64, 895 | mut outmask: u64, 896 | ) -> (f64, f64, f64, f64, f64, f64, f64, f64, f64) { 897 | if !arcmode { 898 | outmask |= caps::DISTANCE_IN 899 | }; 900 | 901 | let line = 902 | geodesic_line::GeodesicLine::new(self, lat1, lon1, azi1, Some(outmask), None, None); 903 | line._gen_position(arcmode, s12_a12, outmask) 904 | } 905 | 906 | /// Get the area of the geodesic in square meters 907 | pub fn area(&self) -> f64 { 908 | self._c2 * 4.0 * std::f64::consts::PI 909 | } 910 | } 911 | 912 | /// Place a second point, given the first point, an azimuth, and a distance. 913 | /// 914 | /// # Arguments 915 | /// - lat1 - Latitude of 1st point (degrees) [-90.,90.] 916 | /// - lon1 - Longitude of 1st point (degrees) [-180., 180.] 917 | /// - azi1 - Azimuth at 1st point (degrees) [-180., 180.] 918 | /// - s12 - Distance from 1st to 2nd point (meters) Value may be negative 919 | /// 920 | /// # Returns 921 | /// 922 | /// There are a variety of outputs associated with this calculation. We save computation by 923 | /// only calculating the outputs you need. See the following impls which return different subsets of 924 | /// the following outputs: 925 | /// 926 | /// - lat2 latitude of point 2 (degrees). 927 | /// - lon2 longitude of point 2 (degrees). 928 | /// - azi2 (forward) azimuth at point 2 (degrees). 929 | /// - m12 reduced length of geodesic (meters). 930 | /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless). 931 | /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless). 932 | /// - S12 area under the geodesic (meters2). 933 | /// - a12 arc length between point 1 and point 2 (degrees). 934 | /// 935 | /// If either point is at a pole, the azimuth is defined by keeping the 936 | /// longitude fixed, writing lat = ±(90° − ε), and taking the limit ε → 0+. 937 | /// An arc length greater that 180° signifies a geodesic which is not a 938 | /// shortest path. (For a prolate ellipsoid, an additional condition is 939 | /// necessary for a shortest path: the longitudinal extent must not 940 | /// exceed of 180°.) 941 | /// ```rust 942 | /// // Example, determine the point 10000 km NE of JFK: 943 | /// use geographiclib_rs::{Geodesic, DirectGeodesic}; 944 | /// 945 | /// let g = Geodesic::wgs84(); 946 | /// let (lat, lon, az) = g.direct(40.64, -73.78, 45.0, 10e6); 947 | /// 948 | /// use approx::assert_relative_eq; 949 | /// assert_relative_eq!(lat, 32.621100463725796); 950 | /// assert_relative_eq!(lon, 49.052487092959836); 951 | /// assert_relative_eq!(az, 140.4059858768007); 952 | /// ``` 953 | pub trait DirectGeodesic { 954 | fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> T; 955 | } 956 | 957 | impl DirectGeodesic<(f64, f64)> for Geodesic { 958 | /// See the documentation for the DirectGeodesic trait. 959 | /// 960 | /// # Returns 961 | /// - lat2 latitude of point 2 (degrees). 962 | /// - lon2 longitude of point 2 (degrees). 963 | fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64) { 964 | let capabilities = caps::LATITUDE | caps::LONGITUDE; 965 | let (_a12, lat2, lon2, _azi2, _s12, _m12, _M12, _M21, _S12) = 966 | self._gen_direct(lat1, lon1, azi1, false, s12, capabilities); 967 | 968 | (lat2, lon2) 969 | } 970 | } 971 | 972 | impl DirectGeodesic<(f64, f64, f64)> for Geodesic { 973 | /// See the documentation for the DirectGeodesic trait. 974 | /// 975 | /// # Returns 976 | /// - lat2 latitude of point 2 (degrees). 977 | /// - lon2 longitude of point 2 (degrees). 978 | /// - azi2 (forward) azimuth at point 2 (degrees). 979 | fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64, f64) { 980 | let capabilities = caps::LATITUDE | caps::LONGITUDE | caps::AZIMUTH; 981 | let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) = 982 | self._gen_direct(lat1, lon1, azi1, false, s12, capabilities); 983 | 984 | (lat2, lon2, azi2) 985 | } 986 | } 987 | 988 | impl DirectGeodesic<(f64, f64, f64, f64)> for Geodesic { 989 | /// See the documentation for the DirectGeodesic trait. 990 | /// 991 | /// # Returns 992 | /// - lat2 latitude of point 2 (degrees). 993 | /// - lon2 longitude of point 2 (degrees). 994 | /// - azi2 (forward) azimuth at point 2 (degrees). 995 | /// - m12 reduced length of geodesic (meters). 996 | fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64, f64, f64) { 997 | let capabilities = caps::LATITUDE | caps::LONGITUDE | caps::AZIMUTH | caps::REDUCEDLENGTH; 998 | let (_a12, lat2, lon2, azi2, _s12, m12, _M12, _M21, _S12) = 999 | self._gen_direct(lat1, lon1, azi1, false, s12, capabilities); 1000 | 1001 | (lat2, lon2, azi2, m12) 1002 | } 1003 | } 1004 | 1005 | impl DirectGeodesic<(f64, f64, f64, f64, f64)> for Geodesic { 1006 | /// See the documentation for the DirectGeodesic trait. 1007 | /// 1008 | /// # Returns 1009 | /// - lat2 latitude of point 2 (degrees). 1010 | /// - lon2 longitude of point 2 (degrees). 1011 | /// - azi2 (forward) azimuth at point 2 (degrees). 1012 | /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless). 1013 | /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless). 1014 | fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64, f64, f64, f64) { 1015 | let capabilities = caps::LATITUDE | caps::LONGITUDE | caps::AZIMUTH | caps::GEODESICSCALE; 1016 | let (_a12, lat2, lon2, azi2, _s12, _m12, M12, M21, _S12) = 1017 | self._gen_direct(lat1, lon1, azi1, false, s12, capabilities); 1018 | 1019 | (lat2, lon2, azi2, M12, M21) 1020 | } 1021 | } 1022 | 1023 | impl DirectGeodesic<(f64, f64, f64, f64, f64, f64)> for Geodesic { 1024 | /// See the documentation for the DirectGeodesic trait. 1025 | /// 1026 | /// # Returns 1027 | /// - lat2 latitude of point 2 (degrees). 1028 | /// - lon2 longitude of point 2 (degrees). 1029 | /// - azi2 (forward) azimuth at point 2 (degrees). 1030 | /// - m12 reduced length of geodesic (meters). 1031 | /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless). 1032 | /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless). 1033 | fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64, f64, f64, f64, f64) { 1034 | let capabilities = caps::LATITUDE 1035 | | caps::LONGITUDE 1036 | | caps::AZIMUTH 1037 | | caps::REDUCEDLENGTH 1038 | | caps::GEODESICSCALE; 1039 | let (_a12, lat2, lon2, azi2, _s12, m12, M12, M21, _S12) = 1040 | self._gen_direct(lat1, lon1, azi1, false, s12, capabilities); 1041 | 1042 | (lat2, lon2, azi2, m12, M12, M21) 1043 | } 1044 | } 1045 | 1046 | impl DirectGeodesic<(f64, f64, f64, f64, f64, f64, f64, f64)> for Geodesic { 1047 | /// See the documentation for the DirectGeodesic trait. 1048 | /// 1049 | /// # Returns 1050 | /// - lat2 latitude of point 2 (degrees). 1051 | /// - lon2 longitude of point 2 (degrees). 1052 | /// - azi2 (forward) azimuth at point 2 (degrees). 1053 | /// - m12 reduced length of geodesic (meters). 1054 | /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless). 1055 | /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless). 1056 | /// - S12 area under the geodesic (meters2). 1057 | /// - a12 arc length between point 1 and point 2 (degrees). 1058 | fn direct( 1059 | &self, 1060 | lat1: f64, 1061 | lon1: f64, 1062 | azi1: f64, 1063 | s12: f64, 1064 | ) -> (f64, f64, f64, f64, f64, f64, f64, f64) { 1065 | let capabilities = caps::LATITUDE 1066 | | caps::LONGITUDE 1067 | | caps::AZIMUTH 1068 | | caps::REDUCEDLENGTH 1069 | | caps::GEODESICSCALE 1070 | | caps::AREA; 1071 | let (a12, lat2, lon2, azi2, _s12, m12, M12, M21, S12) = 1072 | self._gen_direct(lat1, lon1, azi1, false, s12, capabilities); 1073 | 1074 | (lat2, lon2, azi2, m12, M12, M21, S12, a12) 1075 | } 1076 | } 1077 | 1078 | /// Measure the distance (and other values) between two points. 1079 | /// 1080 | /// # Arguments 1081 | /// - lat1 latitude of point 1 (degrees). 1082 | /// - lon1 longitude of point 1 (degrees). 1083 | /// - lat2 latitude of point 2 (degrees). 1084 | /// - lon2 longitude of point 2 (degrees). 1085 | /// 1086 | /// # Returns 1087 | /// 1088 | /// There are a variety of outputs associated with this calculation. We save computation by 1089 | /// only calculating the outputs you need. See the following impls which return different subsets of 1090 | /// the following outputs: 1091 | /// 1092 | /// - s12 distance between point 1 and point 2 (meters). 1093 | /// - azi1 azimuth at point 1 (degrees). 1094 | /// - azi2 (forward) azimuth at point 2 (degrees). 1095 | /// - m12 reduced length of geodesic (meters). 1096 | /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless). 1097 | /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless). 1098 | /// - S12 area under the geodesic (meters2). 1099 | /// - a12 arc length between point 1 and point 2 (degrees). 1100 | /// 1101 | /// `lat1` and `lat2` should be in the range [−90°, 90°]. 1102 | /// The values of `azi1` and `azi2` returned are in the range 1103 | /// [−180°, 180°]. 1104 | /// 1105 | /// If either point is at a pole, the azimuth is defined by keeping the 1106 | /// longitude fixed, writing `lat` = ±(90° − ε), 1107 | /// and taking the limit ε → 0+. 1108 | /// 1109 | /// The solution to the inverse problem is found using Newton's method. If 1110 | /// this fails to converge (this is very unlikely in geodetic applications 1111 | /// but does occur for very eccentric ellipsoids), then the bisection method 1112 | /// is used to refine the solution. 1113 | /// 1114 | /// ```rust 1115 | /// // Example, determine the distance between two points 1116 | /// use geographiclib_rs::{Geodesic, InverseGeodesic}; 1117 | /// 1118 | /// let g = Geodesic::wgs84(); 1119 | /// let p1 = (34.095925, -118.2884237); 1120 | /// let p2 = (59.4323439, 24.7341649); 1121 | /// let s12: f64 = g.inverse(p1.0, p1.1, p2.0, p2.1); 1122 | /// 1123 | /// use approx::assert_relative_eq; 1124 | /// assert_relative_eq!(s12, 9094718.72751138); 1125 | /// ``` 1126 | pub trait InverseGeodesic { 1127 | fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> T; 1128 | } 1129 | 1130 | impl InverseGeodesic for Geodesic { 1131 | /// See the documentation for the InverseGeodesic trait. 1132 | /// 1133 | /// # Returns 1134 | /// - s12 distance between point 1 and point 2 (meters). 1135 | fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 { 1136 | let capabilities = caps::DISTANCE; 1137 | let (_a12, s12, _azi1, _azi2, _m12, _M12, _M21, _S12) = 1138 | self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities); 1139 | 1140 | s12 1141 | } 1142 | } 1143 | 1144 | impl InverseGeodesic<(f64, f64)> for Geodesic { 1145 | /// See the documentation for the InverseGeodesic trait. 1146 | /// 1147 | /// # Returns 1148 | /// - s12 distance between point 1 and point 2 (meters). 1149 | /// - a12 arc length between point 1 and point 2 (degrees). 1150 | fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> (f64, f64) { 1151 | let capabilities = caps::DISTANCE; 1152 | let (a12, s12, _azi1, _azi2, _m12, _M12, _M21, _S12) = 1153 | self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities); 1154 | 1155 | (s12, a12) 1156 | } 1157 | } 1158 | 1159 | impl InverseGeodesic<(f64, f64, f64)> for Geodesic { 1160 | /// See the documentation for the InverseGeodesic trait. 1161 | /// 1162 | /// # Returns 1163 | /// - azi1 azimuth at point 1 (degrees). 1164 | /// - azi2 (forward) azimuth at point 2 (degrees). 1165 | /// - a12 arc length between point 1 and point 2 (degrees). 1166 | fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> (f64, f64, f64) { 1167 | let capabilities = caps::AZIMUTH; 1168 | let (a12, _s12, azi1, azi2, _m12, _M12, _M21, _S12) = 1169 | self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities); 1170 | 1171 | (azi1, azi2, a12) 1172 | } 1173 | } 1174 | 1175 | impl InverseGeodesic<(f64, f64, f64, f64)> for Geodesic { 1176 | /// See the documentation for the InverseGeodesic trait. 1177 | /// 1178 | /// # Returns 1179 | /// - s12 distance between point 1 and point 2 (meters). 1180 | /// - azi1 azimuth at point 1 (degrees). 1181 | /// - azi2 (forward) azimuth at point 2 (degrees). 1182 | /// - a12 arc length between point 1 and point 2 (degrees). 1183 | fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> (f64, f64, f64, f64) { 1184 | let capabilities = caps::DISTANCE | caps::AZIMUTH; 1185 | let (a12, s12, azi1, azi2, _m12, _M12, _M21, _S12) = 1186 | self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities); 1187 | 1188 | (s12, azi1, azi2, a12) 1189 | } 1190 | } 1191 | 1192 | impl InverseGeodesic<(f64, f64, f64, f64, f64)> for Geodesic { 1193 | /// See the documentation for the InverseGeodesic trait. 1194 | /// 1195 | /// # Returns 1196 | /// - s12 distance between point 1 and point 2 (meters). 1197 | /// - azi1 azimuth at point 1 (degrees). 1198 | /// - azi2 (forward) azimuth at point 2 (degrees). 1199 | /// - m12 reduced length of geodesic (meters). 1200 | /// - a12 arc length between point 1 and point 2 (degrees). 1201 | fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> (f64, f64, f64, f64, f64) { 1202 | let capabilities = caps::DISTANCE | caps::AZIMUTH | caps::REDUCEDLENGTH; 1203 | let (a12, s12, azi1, azi2, m12, _M12, _M21, _S12) = 1204 | self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities); 1205 | 1206 | (s12, azi1, azi2, m12, a12) 1207 | } 1208 | } 1209 | 1210 | impl InverseGeodesic<(f64, f64, f64, f64, f64, f64)> for Geodesic { 1211 | /// See the documentation for the InverseGeodesic trait. 1212 | /// 1213 | /// # Returns 1214 | /// - s12 distance between point 1 and point 2 (meters). 1215 | /// - azi1 azimuth at point 1 (degrees). 1216 | /// - azi2 (forward) azimuth at point 2 (degrees). 1217 | /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless). 1218 | /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless). 1219 | /// - a12 arc length between point 1 and point 2 (degrees). 1220 | fn inverse( 1221 | &self, 1222 | lat1: f64, 1223 | lon1: f64, 1224 | lat2: f64, 1225 | lon2: f64, 1226 | ) -> (f64, f64, f64, f64, f64, f64) { 1227 | let capabilities = caps::DISTANCE | caps::AZIMUTH | caps::GEODESICSCALE; 1228 | let (a12, s12, azi1, azi2, _m12, M12, M21, _S12) = 1229 | self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities); 1230 | 1231 | (s12, azi1, azi2, M12, M21, a12) 1232 | } 1233 | } 1234 | 1235 | impl InverseGeodesic<(f64, f64, f64, f64, f64, f64, f64)> for Geodesic { 1236 | /// See the documentation for the InverseGeodesic trait. 1237 | /// 1238 | /// # Returns 1239 | /// - s12 distance between point 1 and point 2 (meters). 1240 | /// - azi1 azimuth at point 1 (degrees). 1241 | /// - azi2 (forward) azimuth at point 2 (degrees). 1242 | /// - m12 reduced length of geodesic (meters). 1243 | /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless). 1244 | /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless). 1245 | /// - a12 arc length between point 1 and point 2 (degrees). 1246 | fn inverse( 1247 | &self, 1248 | lat1: f64, 1249 | lon1: f64, 1250 | lat2: f64, 1251 | lon2: f64, 1252 | ) -> (f64, f64, f64, f64, f64, f64, f64) { 1253 | let capabilities = 1254 | caps::DISTANCE | caps::AZIMUTH | caps::REDUCEDLENGTH | caps::GEODESICSCALE; 1255 | let (a12, s12, azi1, azi2, m12, M12, M21, _S12) = 1256 | self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities); 1257 | 1258 | (s12, azi1, azi2, m12, M12, M21, a12) 1259 | } 1260 | } 1261 | 1262 | impl InverseGeodesic<(f64, f64, f64, f64, f64, f64, f64, f64)> for Geodesic { 1263 | /// See the documentation for the InverseGeodesic trait. 1264 | /// 1265 | /// # Returns 1266 | /// - s12 distance between point 1 and point 2 (meters). 1267 | /// - azi1 azimuth at point 1 (degrees). 1268 | /// - azi2 (forward) azimuth at point 2 (degrees). 1269 | /// - m12 reduced length of geodesic (meters). 1270 | /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless). 1271 | /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless). 1272 | /// - S12 area under the geodesic (meters2). 1273 | /// - a12 arc length between point 1 and point 2 (degrees). 1274 | fn inverse( 1275 | &self, 1276 | lat1: f64, 1277 | lon1: f64, 1278 | lat2: f64, 1279 | lon2: f64, 1280 | ) -> (f64, f64, f64, f64, f64, f64, f64, f64) { 1281 | let capabilities = 1282 | caps::DISTANCE | caps::AZIMUTH | caps::REDUCEDLENGTH | caps::GEODESICSCALE | caps::AREA; 1283 | let (a12, s12, azi1, azi2, m12, M12, M21, S12) = 1284 | self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities); 1285 | 1286 | (s12, azi1, azi2, m12, M12, M21, S12, a12) 1287 | } 1288 | } 1289 | 1290 | #[cfg(test)] 1291 | mod tests { 1292 | use super::*; 1293 | use crate::geodesic_line::GeodesicLine; 1294 | use approx::assert_relative_eq; 1295 | use std::io::BufRead; 1296 | 1297 | #[allow(clippy::type_complexity)] 1298 | const TESTCASES: &[(f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64)] = &[ 1299 | ( 1300 | 35.60777, 1301 | -139.44815, 1302 | 111.098748429560326, 1303 | -11.17491, 1304 | -69.95921, 1305 | 129.289270889708762, 1306 | 8935244.5604818305, 1307 | 80.50729714281974, 1308 | 6273170.2055303837, 1309 | 0.16606318447386067, 1310 | 0.16479116945612937, 1311 | 12841384694976.432, 1312 | ), 1313 | ( 1314 | 55.52454, 1315 | 106.05087, 1316 | 22.020059880982801, 1317 | 77.03196, 1318 | 197.18234, 1319 | 109.112041110671519, 1320 | 4105086.1713924406, 1321 | 36.892740690445894, 1322 | 3828869.3344387607, 1323 | 0.80076349608092607, 1324 | 0.80101006984201008, 1325 | 61674961290615.615, 1326 | ), 1327 | ( 1328 | -21.97856, 1329 | 142.59065, 1330 | -32.44456876433189, 1331 | 41.84138, 1332 | 98.56635, 1333 | -41.84359951440466, 1334 | 8394328.894657671, 1335 | 75.62930491011522, 1336 | 6161154.5773110616, 1337 | 0.24816339233950381, 1338 | 0.24930251203627892, 1339 | -6637997720646.717, 1340 | ), 1341 | ( 1342 | -66.99028, 1343 | 112.2363, 1344 | 173.73491240878403, 1345 | -12.70631, 1346 | 285.90344, 1347 | 2.512956620913668, 1348 | 11150344.2312080241, 1349 | 100.278634181155759, 1350 | 6289939.5670446687, 1351 | -0.17199490274700385, 1352 | -0.17722569526345708, 1353 | -121287239862139.744, 1354 | ), 1355 | ( 1356 | -17.42761, 1357 | 173.34268, 1358 | -159.033557661192928, 1359 | -15.84784, 1360 | 5.93557, 1361 | -20.787484651536988, 1362 | 16076603.1631180673, 1363 | 144.640108810286253, 1364 | 3732902.1583877189, 1365 | -0.81273638700070476, 1366 | -0.81299800519154474, 1367 | 97825992354058.708, 1368 | ), 1369 | ( 1370 | 32.84994, 1371 | 48.28919, 1372 | 150.492927788121982, 1373 | -56.28556, 1374 | 202.29132, 1375 | 48.113449399816759, 1376 | 16727068.9438164461, 1377 | 150.565799985466607, 1378 | 3147838.1910180939, 1379 | -0.87334918086923126, 1380 | -0.86505036767110637, 1381 | -72445258525585.010, 1382 | ), 1383 | ( 1384 | 6.96833, 1385 | 52.74123, 1386 | 92.581585386317712, 1387 | -7.39675, 1388 | 206.17291, 1389 | 90.721692165923907, 1390 | 17102477.2496958388, 1391 | 154.147366239113561, 1392 | 2772035.6169917581, 1393 | -0.89991282520302447, 1394 | -0.89986892177110739, 1395 | -1311796973197.995, 1396 | ), 1397 | ( 1398 | -50.56724, 1399 | -16.30485, 1400 | -105.439679907590164, 1401 | -33.56571, 1402 | -94.97412, 1403 | -47.348547835650331, 1404 | 6455670.5118668696, 1405 | 58.083719495371259, 1406 | 5409150.7979815838, 1407 | 0.53053508035997263, 1408 | 0.52988722644436602, 1409 | 41071447902810.047, 1410 | ), 1411 | ( 1412 | -58.93002, 1413 | -8.90775, 1414 | 140.965397902500679, 1415 | -8.91104, 1416 | 133.13503, 1417 | 19.255429433416599, 1418 | 11756066.0219864627, 1419 | 105.755691241406877, 1420 | 6151101.2270708536, 1421 | -0.26548622269867183, 1422 | -0.27068483874510741, 1423 | -86143460552774.735, 1424 | ), 1425 | ( 1426 | -68.82867, 1427 | -74.28391, 1428 | 93.774347763114881, 1429 | -50.63005, 1430 | -8.36685, 1431 | 34.65564085411343, 1432 | 3956936.926063544, 1433 | 35.572254987389284, 1434 | 3708890.9544062657, 1435 | 0.81443963736383502, 1436 | 0.81420859815358342, 1437 | -41845309450093.787, 1438 | ), 1439 | ( 1440 | -10.62672, 1441 | -32.0898, 1442 | -86.426713286747751, 1443 | 5.883, 1444 | -134.31681, 1445 | -80.473780971034875, 1446 | 11470869.3864563009, 1447 | 103.387395634504061, 1448 | 6184411.6622659713, 1449 | -0.23138683500430237, 1450 | -0.23155097622286792, 1451 | 4198803992123.548, 1452 | ), 1453 | ( 1454 | -21.76221, 1455 | 166.90563, 1456 | 29.319421206936428, 1457 | 48.72884, 1458 | 213.97627, 1459 | 43.508671946410168, 1460 | 9098627.3986554915, 1461 | 81.963476716121964, 1462 | 6299240.9166992283, 1463 | 0.13965943368590333, 1464 | 0.14152969707656796, 1465 | 10024709850277.476, 1466 | ), 1467 | ( 1468 | -19.79938, 1469 | -174.47484, 1470 | 71.167275780171533, 1471 | -11.99349, 1472 | -154.35109, 1473 | 65.589099775199228, 1474 | 2319004.8601169389, 1475 | 20.896611684802389, 1476 | 2267960.8703918325, 1477 | 0.93427001867125849, 1478 | 0.93424887135032789, 1479 | -3935477535005.785, 1480 | ), 1481 | ( 1482 | -11.95887, 1483 | -116.94513, 1484 | 92.712619830452549, 1485 | 4.57352, 1486 | 7.16501, 1487 | 78.64960934409585, 1488 | 13834722.5801401374, 1489 | 124.688684161089762, 1490 | 5228093.177931598, 1491 | -0.56879356755666463, 1492 | -0.56918731952397221, 1493 | -9919582785894.853, 1494 | ), 1495 | ( 1496 | -87.85331, 1497 | 85.66836, 1498 | -65.120313040242748, 1499 | 66.48646, 1500 | 16.09921, 1501 | -4.888658719272296, 1502 | 17286615.3147144645, 1503 | 155.58592449699137, 1504 | 2635887.4729110181, 1505 | -0.90697975771398578, 1506 | -0.91095608883042767, 1507 | 42667211366919.534, 1508 | ), 1509 | ( 1510 | 1.74708, 1511 | 128.32011, 1512 | -101.584843631173858, 1513 | -11.16617, 1514 | 11.87109, 1515 | -86.325793296437476, 1516 | 12942901.1241347408, 1517 | 116.650512484301857, 1518 | 5682744.8413270572, 1519 | -0.44857868222697644, 1520 | -0.44824490340007729, 1521 | 10763055294345.653, 1522 | ), 1523 | ( 1524 | -25.72959, 1525 | -144.90758, 1526 | -153.647468693117198, 1527 | -57.70581, 1528 | -269.17879, 1529 | -48.343983158876487, 1530 | 9413446.7452453107, 1531 | 84.664533838404295, 1532 | 6356176.6898881281, 1533 | 0.09492245755254703, 1534 | 0.09737058264766572, 1535 | 74515122850712.444, 1536 | ), 1537 | ( 1538 | -41.22777, 1539 | 122.32875, 1540 | 14.285113402275739, 1541 | -7.57291, 1542 | 130.37946, 1543 | 10.805303085187369, 1544 | 3812686.035106021, 1545 | 34.34330804743883, 1546 | 3588703.8812128856, 1547 | 0.82605222593217889, 1548 | 0.82572158200920196, 1549 | -2456961531057.857, 1550 | ), 1551 | ( 1552 | 11.01307, 1553 | 138.25278, 1554 | 79.43682622782374, 1555 | 6.62726, 1556 | 247.05981, 1557 | 103.708090215522657, 1558 | 11911190.819018408, 1559 | 107.341669954114577, 1560 | 6070904.722786735, 1561 | -0.29767608923657404, 1562 | -0.29785143390252321, 1563 | 17121631423099.696, 1564 | ), 1565 | ( 1566 | -29.47124, 1567 | 95.14681, 1568 | -163.779130441688382, 1569 | -27.46601, 1570 | -69.15955, 1571 | -15.909335945554969, 1572 | 13487015.8381145492, 1573 | 121.294026715742277, 1574 | 5481428.9945736388, 1575 | -0.51527225545373252, 1576 | -0.51556587964721788, 1577 | 104679964020340.318, 1578 | ), 1579 | ]; 1580 | 1581 | #[test] 1582 | fn test_inverse_and_direct() -> Result<(), String> { 1583 | // See python/test_geodesic.py 1584 | let geod = Geodesic::wgs84(); 1585 | let (_a12, s12, _azi1, _azi2, _m12, _M12, _M21, _S12) = 1586 | geod._gen_inverse_azi(0.0, 0.0, 1.0, 1.0, caps::STANDARD); 1587 | assert_eq!(s12, 156899.56829134026); 1588 | 1589 | // Test inverse 1590 | for (lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12) in TESTCASES.iter() { 1591 | let ( 1592 | computed_a12, 1593 | computed_s12, 1594 | computed_azi1, 1595 | computed_azi2, 1596 | computed_m12, 1597 | computed_M12, 1598 | computed_M21, 1599 | computed_S12, 1600 | ) = geod._gen_inverse_azi(*lat1, *lon1, *lat2, *lon2, caps::ALL | caps::LONG_UNROLL); 1601 | assert_relative_eq!(computed_azi1, azi1, epsilon = 1e-13f64); 1602 | assert_relative_eq!(computed_azi2, azi2, epsilon = 1e-13f64); 1603 | assert_relative_eq!(computed_s12, s12, epsilon = 1e-8f64); 1604 | assert_relative_eq!(computed_a12, a12, epsilon = 1e-13f64); 1605 | assert_relative_eq!(computed_m12, m12, epsilon = 1e-8f64); 1606 | assert_relative_eq!(computed_M12, M12, epsilon = 1e-15f64); 1607 | assert_relative_eq!(computed_M21, M21, epsilon = 1e-15f64); 1608 | assert_relative_eq!(computed_S12, S12, epsilon = 0.1f64); 1609 | } 1610 | 1611 | // Test direct 1612 | for (lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12) in TESTCASES.iter() { 1613 | let ( 1614 | computed_a12, 1615 | computed_lat2, 1616 | computed_lon2, 1617 | computed_azi2, 1618 | _computed_s12, 1619 | computed_m12, 1620 | computed_M12, 1621 | computed_M21, 1622 | computed_S12, 1623 | ) = geod._gen_direct( 1624 | *lat1, 1625 | *lon1, 1626 | *azi1, 1627 | false, 1628 | *s12, 1629 | caps::ALL | caps::LONG_UNROLL, 1630 | ); 1631 | assert_relative_eq!(computed_lat2, lat2, epsilon = 1e-13f64); 1632 | assert_relative_eq!(computed_lon2, lon2, epsilon = 1e-13f64); 1633 | assert_relative_eq!(computed_azi2, azi2, epsilon = 1e-13f64); 1634 | assert_relative_eq!(computed_a12, a12, epsilon = 1e-13f64); 1635 | assert_relative_eq!(computed_m12, m12, epsilon = 1e-8f64); 1636 | assert_relative_eq!(computed_M12, M12, epsilon = 1e-15f64); 1637 | assert_relative_eq!(computed_M21, M21, epsilon = 1e-15f64); 1638 | assert_relative_eq!(computed_S12, S12, epsilon = 0.1f64); 1639 | } 1640 | Ok(()) 1641 | } 1642 | 1643 | #[test] 1644 | fn test_arcdirect() { 1645 | // Corresponds with ArcDirectCheck from Java, or test_arcdirect from Python 1646 | let geod = Geodesic::wgs84(); 1647 | for (lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12) in TESTCASES.iter() { 1648 | let ( 1649 | _computed_a12, 1650 | computed_lat2, 1651 | computed_lon2, 1652 | computed_azi2, 1653 | computed_s12, 1654 | computed_m12, 1655 | computed_M12, 1656 | computed_M21, 1657 | computed_S12, 1658 | ) = geod._gen_direct( 1659 | *lat1, 1660 | *lon1, 1661 | *azi1, 1662 | true, 1663 | *a12, 1664 | caps::ALL | caps::LONG_UNROLL, 1665 | ); 1666 | assert_relative_eq!(computed_lat2, lat2, epsilon = 1e-13); 1667 | assert_relative_eq!(computed_lon2, lon2, epsilon = 1e-13); 1668 | assert_relative_eq!(computed_azi2, azi2, epsilon = 1e-13); 1669 | assert_relative_eq!(computed_s12, s12, epsilon = 1e-8); 1670 | assert_relative_eq!(computed_m12, m12, epsilon = 1e-8); 1671 | assert_relative_eq!(computed_M12, M12, epsilon = 1e-15); 1672 | assert_relative_eq!(computed_M21, M21, epsilon = 1e-15); 1673 | assert_relative_eq!(computed_S12, S12, epsilon = 0.1); 1674 | } 1675 | } 1676 | 1677 | #[test] 1678 | fn test_geninverse() { 1679 | let geod = Geodesic::wgs84(); 1680 | let res = geod._gen_inverse(0.0, 0.0, 1.0, 1.0, caps::STANDARD); 1681 | assert_eq!(res.0, 1.4141938478710363); 1682 | assert_eq!(res.1, 156899.56829134026); 1683 | assert_eq!(res.2, 0.7094236375834774); 1684 | assert_eq!(res.3, 0.7047823085448635); 1685 | assert_eq!(res.4, 0.7095309793242709); 1686 | assert_eq!(res.5, 0.7046742434480923); 1687 | assert!(res.6.is_nan()); 1688 | assert!(res.7.is_nan()); 1689 | assert!(res.8.is_nan()); 1690 | assert!(res.9.is_nan()); 1691 | } 1692 | 1693 | #[test] 1694 | fn test_inverse_start() { 1695 | let geod = Geodesic::wgs84(); 1696 | let res = geod._InverseStart( 1697 | -0.017393909556108908, 1698 | 0.9998487145115275, 1699 | 1.0000010195104125, 1700 | -0.0, 1701 | 1.0, 1702 | 1.0, 1703 | 0.017453292519943295, 1704 | 0.01745240643728351, 1705 | 0.9998476951563913, 1706 | &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 1707 | &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 1708 | ); 1709 | assert_eq!(res.0, -1.0); 1710 | assert_relative_eq!(res.1, 0.7095310092765433, epsilon = 1e-13); 1711 | assert_relative_eq!(res.2, 0.7046742132893822, epsilon = 1e-13); 1712 | assert!(res.3.is_nan()); 1713 | assert!(res.4.is_nan()); 1714 | assert_eq!(res.5, 1.0000002548969817); 1715 | 1716 | let res = geod._InverseStart( 1717 | -0.017393909556108908, 1718 | 0.9998487145115275, 1719 | 1.0000010195104125, 1720 | -0.0, 1721 | 1.0, 1722 | 1.0, 1723 | 0.017453292519943295, 1724 | 0.01745240643728351, 1725 | 0.9998476951563913, 1726 | &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 1727 | &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 1728 | ); 1729 | assert_eq!(res.0, -1.0); 1730 | assert_relative_eq!(res.1, 0.7095310092765433, epsilon = 1e-13); 1731 | assert_relative_eq!(res.2, 0.7046742132893822, epsilon = 1e-13); 1732 | assert!(res.3.is_nan()); 1733 | assert!(res.4.is_nan()); 1734 | assert_eq!(res.5, 1.0000002548969817); 1735 | } 1736 | 1737 | #[test] 1738 | fn test_lambda12() { 1739 | let geod = Geodesic::wgs84(); 1740 | let res1 = geod._Lambda12( 1741 | -0.017393909556108908, 1742 | 0.9998487145115275, 1743 | 1.0000010195104125, 1744 | -0.0, 1745 | 1.0, 1746 | 1.0, 1747 | 0.7095310092765433, 1748 | 0.7046742132893822, 1749 | 0.01745240643728351, 1750 | 0.9998476951563913, 1751 | true, 1752 | &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 1753 | &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 1754 | &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 1755 | ); 1756 | assert_eq!(res1.0, 1.4834408705897495e-09); 1757 | assert_eq!(res1.1, 0.7094236675312185); 1758 | assert_eq!(res1.2, 0.7047822783999007); 1759 | assert_eq!(res1.3, 0.024682339962725352); 1760 | assert_eq!(res1.4, -0.024679833885152578); 1761 | assert_eq!(res1.5, 0.9996954065111039); 1762 | assert_eq!(res1.6, -0.0); 1763 | assert_eq!(res1.7, 1.0); 1764 | assert_relative_eq!(res1.8, 0.0008355095326524276, epsilon = 1e-13); 1765 | assert_eq!(res1.9, -5.8708496511415445e-05); 1766 | assert_eq!(res1.10, 0.034900275148485); 1767 | 1768 | let res2 = geod._Lambda12( 1769 | -0.017393909556108908, 1770 | 0.9998487145115275, 1771 | 1.0000010195104125, 1772 | -0.0, 1773 | 1.0, 1774 | 1.0, 1775 | 0.7095309793242709, 1776 | 0.7046742434480923, 1777 | 0.01745240643728351, 1778 | 0.9998476951563913, 1779 | true, 1780 | &mut [ 1781 | 0.0, 1782 | -0.00041775465696698233, 1783 | -4.362974596862037e-08, 1784 | -1.2151022357848552e-11, 1785 | -4.7588881620421004e-15, 1786 | -2.226614930167366e-18, 1787 | -1.1627237498131586e-21, 1788 | ], 1789 | &mut [ 1790 | 0.0, 1791 | -0.0008355098973052918, 1792 | -1.7444619952659748e-07, 1793 | -7.286557795511902e-11, 1794 | -3.80472772706481e-14, 1795 | -2.2251271876594078e-17, 1796 | 1.2789961247944744e-20, 1797 | ], 1798 | &mut [ 1799 | 0.0, 1800 | 0.00020861391868413911, 1801 | 4.3547247296823945e-08, 1802 | 1.515432276542012e-11, 1803 | 6.645637323698485e-15, 1804 | 3.3399223952510497e-18, 1805 | ], 1806 | ); 1807 | assert_eq!(res2.0, 6.046459990680098e-17); 1808 | assert_eq!(res2.1, 0.7094236375834774); 1809 | assert_eq!(res2.2, 0.7047823085448635); 1810 | assert_eq!(res2.3, 0.024682338906797385); 1811 | assert_eq!(res2.4, -0.02467983282954624); 1812 | assert_eq!(res2.5, 0.9996954065371639); 1813 | assert_eq!(res2.6, -0.0); 1814 | assert_eq!(res2.7, 1.0); 1815 | assert_relative_eq!(res2.8, 0.0008355096040059597, epsilon = 1e-18); 1816 | assert_eq!(res2.9, -5.870849152149326e-05); 1817 | assert_eq!(res2.10, 0.03490027216297455); 1818 | } 1819 | 1820 | #[test] 1821 | fn test_lengths() { 1822 | // Results taken from the python implementation 1823 | let geod = Geodesic::wgs84(); 1824 | let mut c1a = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; 1825 | let mut c2a = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; 1826 | let res1 = geod._Lengths( 1827 | 0.0008355095326524276, 1828 | 0.024682339962725352, 1829 | -0.024679833885152578, 1830 | 0.9996954065111039, 1831 | 1.0000010195104125, 1832 | -0.0, 1833 | 1.0, 1834 | 1.0, 1835 | 0.9998487145115275, 1836 | 1.0, 1837 | 4101, 1838 | &mut c1a, 1839 | &mut c2a, 1840 | ); 1841 | assert!(res1.0.is_nan()); 1842 | assert_eq!(res1.1, 0.024679842274314294); 1843 | assert_eq!(res1.2, 0.0016717180169067588); 1844 | assert!(res1.3.is_nan()); 1845 | assert!(res1.4.is_nan()); 1846 | 1847 | let res2 = geod._Lengths( 1848 | 0.0008355096040059597, 1849 | 0.024682338906797385, 1850 | -0.02467983282954624, 1851 | 0.9996954065371639, 1852 | 1.0000010195104125, 1853 | -0.0, 1854 | 1.0, 1855 | 1.0, 1856 | 0.9998487145115275, 1857 | 1.0, 1858 | 4101, 1859 | &mut [ 1860 | 0.0, 1861 | -0.00041775465696698233, 1862 | -4.362974596862037e-08, 1863 | -1.2151022357848552e-11, 1864 | -4.7588881620421004e-15, 1865 | -2.226614930167366e-18, 1866 | -1.1627237498131586e-21, 1867 | ], 1868 | &mut [ 1869 | 0.0, 1870 | -0.0008355098973052918, 1871 | -1.7444619952659748e-07, 1872 | -7.286557795511902e-11, 1873 | -3.80472772706481e-14, 1874 | -2.2251271876594078e-17, 1875 | 1.2789961247944744e-20, 1876 | ], 1877 | ); 1878 | assert!(res2.0.is_nan()); 1879 | assert_eq!(res2.1, 0.02467984121870759); 1880 | assert_eq!(res2.2, 0.0016717181597332804); 1881 | assert!(res2.3.is_nan()); 1882 | assert!(res2.4.is_nan()); 1883 | 1884 | let res3 = geod._Lengths( 1885 | 0.0008355096040059597, 1886 | 0.024682338906797385, 1887 | -0.02467983282954624, 1888 | 0.9996954065371639, 1889 | 1.0000010195104125, 1890 | -0.0, 1891 | 1.0, 1892 | 1.0, 1893 | 0.9998487145115275, 1894 | 1.0, 1895 | 1920, 1896 | &mut [ 1897 | 0.0, 1898 | -0.00041775469264372037, 1899 | -4.362975342068502e-08, 1900 | -1.215102547098435e-11, 1901 | -4.758889787701359e-15, 1902 | -2.2266158809456692e-18, 1903 | -1.1627243456014359e-21, 1904 | ], 1905 | &mut [ 1906 | 0.0, 1907 | -0.0008355099686589174, 1908 | -1.744462293162189e-07, 1909 | -7.286559662008413e-11, 1910 | -3.804729026574989e-14, 1911 | -2.2251281376754273e-17, 1912 | 1.2789967801615795e-20, 1913 | ], 1914 | ); 1915 | assert_eq!(res3.0, 0.024682347295447677); 1916 | assert!(res3.1.is_nan()); 1917 | assert!(res3.2.is_nan()); 1918 | assert!(res3.3.is_nan()); 1919 | assert!(res3.4.is_nan()); 1920 | 1921 | let res = geod._Lengths( 1922 | 0.0007122620325664751, 1923 | 1.405117407023628, 1924 | -0.8928657853278468, 1925 | 0.45032287238256896, 1926 | 1.0011366173804046, 1927 | 0.2969032234925426, 1928 | 0.9549075745221299, 1929 | 1.0001257451360057, 1930 | 0.8139459053827204, 1931 | 0.9811634781422108, 1932 | 1920, 1933 | &mut [ 1934 | 0.0, 1935 | -0.0003561309485314716, 1936 | -3.170731714689771e-08, 1937 | -7.527972480734327e-12, 1938 | -2.5133854116682488e-15, 1939 | -1.0025061462383107e-18, 1940 | -4.462794158625518e-22, 1941 | ], 1942 | &mut [ 1943 | 0.0, 1944 | -0.0007122622584701569, 1945 | -1.2678416507678478e-07, 1946 | -4.514641118748122e-11, 1947 | -2.0096353119518367e-14, 1948 | -1.0019350865558619e-17, 1949 | 4.90907357448807e-21, 1950 | ], 1951 | ); 1952 | assert_eq!(res.0, 1.4056304412645388); 1953 | assert!(res.1.is_nan()); 1954 | assert!(res.2.is_nan()); 1955 | assert!(res.3.is_nan()); 1956 | assert!(res.4.is_nan()); 1957 | } 1958 | 1959 | #[test] 1960 | fn test_goed__C4f() { 1961 | let geod = Geodesic::wgs84(); 1962 | let mut c = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]; 1963 | geod._C4f(0.12, &mut c); 1964 | assert_eq!( 1965 | c, 1966 | [ 1967 | 0.6420952961066771, 1968 | 0.0023680700061156517, 1969 | 9.96704067834604e-05, 1970 | 5.778187189466089e-06, 1971 | 3.9979026199316593e-07, 1972 | 3.2140078103714466e-08, 1973 | 7.0 1974 | ] 1975 | ); 1976 | } 1977 | 1978 | #[test] 1979 | fn test_goed__C3f() { 1980 | let geod = Geodesic::wgs84(); 1981 | let mut c = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]; 1982 | geod._C3f(0.12, &mut c); 1983 | 1984 | assert_eq!( 1985 | c, 1986 | [ 1987 | 1.0, 1988 | 0.031839442894193756, 1989 | 0.0009839921354137713, 1990 | 5.0055242248766214e-05, 1991 | 3.1656788204092044e-06, 1992 | 2.0412e-07, 1993 | 7.0 1994 | ] 1995 | ); 1996 | } 1997 | 1998 | #[test] 1999 | fn test_goed__A3f() { 2000 | let geod = Geodesic::wgs84(); 2001 | assert_eq!(geod._A3f(0.12), 0.9363788874000158); 2002 | } 2003 | 2004 | #[test] 2005 | fn test_geod_init() { 2006 | // Check that after the init the variables are correctly set. 2007 | // Actual values are taken from the python implementation 2008 | let geod = Geodesic::wgs84(); 2009 | assert_eq!(geod.a, 6378137.0, "geod.a wrong"); 2010 | assert_eq!(geod.f, 0.0033528106647474805, "geod.f wrong"); 2011 | assert_eq!(geod._f1, 0.9966471893352525, "geod._f1 wrong"); 2012 | assert_eq!(geod._e2, 0.0066943799901413165, "geod._e2 wrong"); 2013 | assert_eq!(geod._ep2, 0.006739496742276434, "geod._ep2 wrong"); 2014 | assert_eq!(geod._n, 0.0016792203863837047, "geod._n wrong"); 2015 | assert_eq!(geod._b, 6356752.314245179, "geod._b wrong"); 2016 | assert_eq!(geod._c2, 40589732499314.76, "geod._c2 wrong"); 2017 | assert_eq!(geod._etol2, 3.6424611488788524e-08, "geod._etol2 wrong"); 2018 | assert_eq!( 2019 | geod._A3x, 2020 | [ 2021 | -0.0234375, 2022 | -0.046927475637074494, 2023 | -0.06281503005876607, 2024 | -0.2502088451303832, 2025 | -0.49916038980680816, 2026 | 1.0 2027 | ], 2028 | "geod._A3x wrong" 2029 | ); 2030 | 2031 | assert_eq!( 2032 | geod._C3x, 2033 | [ 2034 | 0.0234375, 2035 | 0.03908873781853724, 2036 | 0.04695366939653196, 2037 | 0.12499964752736174, 2038 | 0.24958019490340408, 2039 | 0.01953125, 2040 | 0.02345061890926862, 2041 | 0.046822392185686165, 2042 | 0.062342661206936094, 2043 | 0.013671875, 2044 | 0.023393770302437927, 2045 | 0.025963026642854565, 2046 | 0.013671875, 2047 | 0.01362595881755982, 2048 | 0.008203125 2049 | ], 2050 | "geod._C3x wrong" 2051 | ); 2052 | assert_eq!( 2053 | geod._C4x, 2054 | [ 2055 | 0.00646020646020646, 2056 | 0.0035037627212872787, 2057 | 0.034742279454780166, 2058 | -0.01921732223244865, 2059 | -0.19923321555984239, 2060 | 0.6662190894642603, 2061 | 0.000111000111000111, 2062 | 0.003426620602971002, 2063 | -0.009510765372597735, 2064 | -0.01893413691235592, 2065 | 0.0221370239510936, 2066 | 0.0007459207459207459, 2067 | -0.004142006291321442, 2068 | -0.00504225176309005, 2069 | 0.007584982177746079, 2070 | -0.0021565735851450138, 2071 | -0.001962613370670692, 2072 | 0.0036104265913438913, 2073 | -0.0009472009472009472, 2074 | 0.0020416649913317735, 2075 | 0.0012916376552740189 2076 | ], 2077 | "geod._C4x wrong" 2078 | ); 2079 | } 2080 | 2081 | // The test_std_geodesic_* tests below are based on Karney's GeodSolve unit 2082 | // tests, found in many geographiclib variants. 2083 | // The versions below are mostly adapted from their Java counterparts, 2084 | // which use a testing structure more similar to Rust than do the C++ versions. 2085 | // Note that the Java tests often incorporate more than one of the C++ tests, 2086 | // and take their name from the lowest-numbered test in the set. 2087 | // These tests use that convention as well. 2088 | 2089 | #[test] 2090 | fn test_std_geodesic_geodsolve0() { 2091 | let geod = Geodesic::wgs84(); 2092 | let (s12, azi1, azi2, _a12) = geod.inverse(40.6, -73.8, 49.01666667, 2.55); 2093 | assert_relative_eq!(azi1, 53.47022, epsilon = 0.5e-5); 2094 | assert_relative_eq!(azi2, 111.59367, epsilon = 0.5e-5); 2095 | assert_relative_eq!(s12, 5853226.0, epsilon = 0.5); 2096 | } 2097 | 2098 | #[test] 2099 | fn test_std_geodesic_geodsolve1() { 2100 | let geod = Geodesic::wgs84(); 2101 | let (lat2, lon2, azi2) = geod.direct(40.63972222, -73.77888889, 53.5, 5850e3); 2102 | assert_relative_eq!(lat2, 49.01467, epsilon = 0.5e-5); 2103 | assert_relative_eq!(lon2, 2.56106, epsilon = 0.5e-5); 2104 | assert_relative_eq!(azi2, 111.62947, epsilon = 0.5e-5); 2105 | } 2106 | 2107 | #[test] 2108 | fn test_std_geodesic_geodsolve2() { 2109 | // Check fix for antipodal prolate bug found 2010-09-04 2110 | let geod = Geodesic::new(6.4e6, -1f64 / 150.0); 2111 | let (s12, azi1, azi2, _a12) = geod.inverse(0.07476, 0.0, -0.07476, 180.0); 2112 | assert_relative_eq!(azi1, 90.00078, epsilon = 0.5e-5); 2113 | assert_relative_eq!(azi2, 90.00078, epsilon = 0.5e-5); 2114 | assert_relative_eq!(s12, 20106193.0, epsilon = 0.5); 2115 | let (s12, azi1, azi2, _a12) = geod.inverse(0.1, 0.0, -0.1, 180.0); 2116 | assert_relative_eq!(azi1, 90.00105, epsilon = 0.5e-5); 2117 | assert_relative_eq!(azi2, 90.00105, epsilon = 0.5e-5); 2118 | assert_relative_eq!(s12, 20106193.0, epsilon = 0.5); 2119 | } 2120 | 2121 | #[test] 2122 | fn test_std_geodesic_geodsolve4() { 2123 | // Check fix for short line bug found 2010-05-21 2124 | let geod = Geodesic::wgs84(); 2125 | let s12: f64 = geod.inverse(36.493349428792, 0.0, 36.49334942879201, 0.0000008); 2126 | assert_relative_eq!(s12, 0.072, epsilon = 0.5e-3); 2127 | } 2128 | 2129 | #[test] 2130 | fn test_std_geodesic_geodsolve5() { 2131 | // Check fix for point2=pole bug found 2010-05-03 2132 | let geod = Geodesic::wgs84(); 2133 | let (lat2, lon2, azi2) = geod.direct(0.01777745589997, 30.0, 0.0, 10e6); 2134 | assert_relative_eq!(lat2, 90.0, epsilon = 0.5e-5); 2135 | if lon2 < 0.0 { 2136 | assert_relative_eq!(lon2, -150.0, epsilon = 0.5e-5); 2137 | assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5); 2138 | } else { 2139 | assert_relative_eq!(lon2, 30.0, epsilon = 0.5e-5); 2140 | assert_relative_eq!(azi2, 0.0, epsilon = 0.5e-5); 2141 | } 2142 | } 2143 | 2144 | #[test] 2145 | fn test_std_geodesic_geodsolve6() { 2146 | // Check fix for volatile sbet12a bug found 2011-06-25 (gcc 4.4.4 2147 | // x86 -O3). Found again on 2012-03-27 with tdm-mingw32 (g++ 4.6.1). 2148 | let geod = Geodesic::wgs84(); 2149 | let s12: f64 = geod.inverse( 2150 | 88.202499451857, 2151 | 0.0, 2152 | -88.202499451857, 2153 | 179.981022032992859592, 2154 | ); 2155 | assert_relative_eq!(s12, 20003898.214, epsilon = 0.5e-3); 2156 | let s12: f64 = geod.inverse( 2157 | 89.333123580033, 2158 | 0.0, 2159 | -89.333123580032997687, 2160 | 179.99295812360148422, 2161 | ); 2162 | assert_relative_eq!(s12, 20003926.881, epsilon = 0.5e-3); 2163 | } 2164 | 2165 | #[test] 2166 | fn test_std_geodesic_geodsolve9() { 2167 | // Check fix for volatile x bug found 2011-06-25 (gcc 4.4.4 x86 -O3) 2168 | let geod = Geodesic::wgs84(); 2169 | let s12: f64 = geod.inverse( 2170 | 56.320923501171, 2171 | 0.0, 2172 | -56.320923501171, 2173 | 179.664747671772880215, 2174 | ); 2175 | assert_relative_eq!(s12, 19993558.287, epsilon = 0.5e-3); 2176 | } 2177 | 2178 | #[test] 2179 | fn test_std_geodesic_geodsolve10() { 2180 | // Check fix for adjust tol1_ bug found 2011-06-25 (Visual Studio 2181 | // 10 rel + debug) 2182 | let geod = Geodesic::wgs84(); 2183 | let s12: f64 = geod.inverse( 2184 | 52.784459512564, 2185 | 0.0, 2186 | -52.784459512563990912, 2187 | 179.634407464943777557, 2188 | ); 2189 | assert_relative_eq!(s12, 19991596.095, epsilon = 0.5e-3); 2190 | } 2191 | 2192 | #[test] 2193 | fn test_std_geodesic_geodsolve11() { 2194 | // Check fix for bet2 = -bet1 bug found 2011-06-25 (Visual Studio 2195 | // 10 rel + debug) 2196 | let geod = Geodesic::wgs84(); 2197 | let s12: f64 = geod.inverse( 2198 | 48.522876735459, 2199 | 0.0, 2200 | -48.52287673545898293, 2201 | 179.599720456223079643, 2202 | ); 2203 | assert_relative_eq!(s12, 19989144.774, epsilon = 0.5e-3); 2204 | } 2205 | 2206 | #[test] 2207 | fn test_std_geodesic_geodsolve12() { 2208 | // Check fix for inverse geodesics on extreme prolate/oblate 2209 | // ellipsoids Reported 2012-08-29 Stefan Guenther 2210 | // ; fixed 2012-10-07 2211 | let geod = Geodesic::new(89.8, -1.83); 2212 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, -10.0, 160.0); 2213 | assert_relative_eq!(azi1, 120.27, epsilon = 1e-2); 2214 | assert_relative_eq!(azi2, 105.15, epsilon = 1e-2); 2215 | assert_relative_eq!(s12, 266.7, epsilon = 1e-1); 2216 | } 2217 | 2218 | #[test] 2219 | fn test_std_geodesic_geodsolve14() { 2220 | // Check fix for inverse ignoring lon12 = nan 2221 | let geod = Geodesic::wgs84(); 2222 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 1.0, f64::NAN); 2223 | assert!(azi1.is_nan()); 2224 | assert!(azi2.is_nan()); 2225 | assert!(s12.is_nan()); 2226 | } 2227 | 2228 | #[test] 2229 | fn test_std_geodesic_geodsolve15() { 2230 | // Initial implementation of Math::eatanhe was wrong for e^2 < 0. This 2231 | // checks that this is fixed. 2232 | let geod = Geodesic::new(6.4e6, -1f64 / 150.0); 2233 | let (_lat2, _lon2, _azi2, _m12, _M12, _M21, S12, _a12) = geod.direct(1.0, 2.0, 3.0, 4.0); 2234 | assert_relative_eq!(S12, 23700.0, epsilon = 0.5); 2235 | } 2236 | 2237 | #[test] 2238 | fn test_std_geodesic_geodsolve17() { 2239 | // Check fix for LONG_UNROLL bug found on 2015-05-07 2240 | let geod = Geodesic::new(6.4e6, -1f64 / 150.0); 2241 | let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) = geod._gen_direct( 2242 | 40.0, 2243 | -75.0, 2244 | -10.0, 2245 | false, 2246 | 2e7, 2247 | caps::STANDARD | caps::LONG_UNROLL, 2248 | ); 2249 | assert_relative_eq!(lat2, -39.0, epsilon = 1.0); 2250 | assert_relative_eq!(lon2, -254.0, epsilon = 1.0); 2251 | assert_relative_eq!(azi2, -170.0, epsilon = 1.0); 2252 | 2253 | let line = GeodesicLine::new(&geod, 40.0, -75.0, -10.0, None, None, None); 2254 | let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) = 2255 | line._gen_position(false, 2e7, caps::STANDARD | caps::LONG_UNROLL); 2256 | assert_relative_eq!(lat2, -39.0, epsilon = 1.0); 2257 | assert_relative_eq!(lon2, -254.0, epsilon = 1.0); 2258 | assert_relative_eq!(azi2, -170.0, epsilon = 1.0); 2259 | 2260 | let (lat2, lon2, azi2) = geod.direct(40.0, -75.0, -10.0, 2e7); 2261 | assert_relative_eq!(lat2, -39.0, epsilon = 1.0); 2262 | assert_relative_eq!(lon2, 105.0, epsilon = 1.0); 2263 | assert_relative_eq!(azi2, -170.0, epsilon = 1.0); 2264 | 2265 | let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) = 2266 | line._gen_position(false, 2e7, caps::STANDARD); 2267 | assert_relative_eq!(lat2, -39.0, epsilon = 1.0); 2268 | assert_relative_eq!(lon2, 105.0, epsilon = 1.0); 2269 | assert_relative_eq!(azi2, -170.0, epsilon = 1.0); 2270 | } 2271 | 2272 | #[test] 2273 | fn test_std_geodesic_geodsolve26() { 2274 | // Check 0/0 problem with area calculation on sphere 2015-09-08 2275 | let geod = Geodesic::new(6.4e6, 0.0); 2276 | let (_a12, _s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, S12) = 2277 | geod._gen_inverse(1.0, 2.0, 3.0, 4.0, caps::AREA); 2278 | assert_relative_eq!(S12, 49911046115.0, epsilon = 0.5); 2279 | } 2280 | 2281 | #[test] 2282 | fn test_std_geodesic_geodsolve28() { 2283 | // Check for bad placement of assignment of r.a12 with |f| > 0.01 (bug in 2284 | // Java implementation fixed on 2015-05-19). 2285 | let geod = Geodesic::new(6.4e6, 0.1); 2286 | let (a12, _lat2, _lon2, _azi2, _s12, _m12, _M12, _M21, _S12) = 2287 | geod._gen_direct(1.0, 2.0, 10.0, false, 5e6, caps::STANDARD); 2288 | assert_relative_eq!(a12, 48.55570690, epsilon = 0.5e-8); 2289 | } 2290 | 2291 | #[test] 2292 | fn test_std_geodesic_geodsolve29() { 2293 | // Check longitude unrolling with inverse calculation 2015-09-16 2294 | let geod = Geodesic::wgs84(); 2295 | let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, _S12) = 2296 | geod._gen_inverse(0.0, 539.0, 0.0, 181.0, caps::STANDARD); 2297 | // Note: This is also supposed to check adjusted longitudes, but geographiclib-rs 2298 | // doesn't seem to support that as of 2021/01/18. 2299 | // assert_relative_eq!(lon1, 179, epsilon = 1e-10); 2300 | // assert_relative_eq!(lon2, -179, epsilon = 1e-10); 2301 | assert_relative_eq!(s12, 222639.0, epsilon = 0.5); 2302 | let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, _S12) = 2303 | geod._gen_inverse(0.0, 539.0, 0.0, 181.0, caps::STANDARD | caps::LONG_UNROLL); 2304 | // assert_relative_eq!(lon1, 539, epsilon = 1e-10); 2305 | // assert_relative_eq!(lon2, 541, epsilon = 1e-10); 2306 | assert_relative_eq!(s12, 222639.0, epsilon = 0.5); 2307 | } 2308 | 2309 | #[test] 2310 | fn test_std_geodesic_geodsolve33() { 2311 | // Check max(-0.0,+0.0) issues 2015-08-22 (triggered by bugs in Octave -- 2312 | // sind(-0.0) = +0.0 -- and in some version of Visual Studio -- 2313 | // fmod(-0.0, 360.0) = +0.0. 2314 | let geod = Geodesic::wgs84(); 2315 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 179.0); 2316 | assert_relative_eq!(azi1, 90.0, epsilon = 0.5e-5); 2317 | assert_relative_eq!(azi2, 90.0, epsilon = 0.5e-5); 2318 | assert_relative_eq!(s12, 19926189.0, epsilon = 0.5); 2319 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 179.5); 2320 | assert_relative_eq!(azi1, 55.96650, epsilon = 0.5e-5); 2321 | assert_relative_eq!(azi2, 124.03350, epsilon = 0.5e-5); 2322 | assert_relative_eq!(s12, 19980862.0, epsilon = 0.5); 2323 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 180.0); 2324 | assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5); 2325 | assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5); 2326 | assert_relative_eq!(s12, 20003931.0, epsilon = 0.5); 2327 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 1.0, 180.0); 2328 | assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5); 2329 | assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5); 2330 | assert_relative_eq!(s12, 19893357.0, epsilon = 0.5); 2331 | 2332 | let geod = Geodesic::new(6.4e6, 0.0); 2333 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 179.0); 2334 | assert_relative_eq!(azi1, 90.0, epsilon = 0.5e-5); 2335 | assert_relative_eq!(azi2, 90.0, epsilon = 0.5e-5); 2336 | assert_relative_eq!(s12, 19994492.0, epsilon = 0.5); 2337 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 180.0); 2338 | assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5); 2339 | assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5); 2340 | assert_relative_eq!(s12, 20106193.0, epsilon = 0.5); 2341 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 1.0, 180.0); 2342 | assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5); 2343 | assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5); 2344 | assert_relative_eq!(s12, 19994492.0, epsilon = 0.5); 2345 | 2346 | let geod = Geodesic::new(6.4e6, -1.0 / 300.0); 2347 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 179.0); 2348 | assert_relative_eq!(azi1, 90.0, epsilon = 0.5e-5); 2349 | assert_relative_eq!(azi2, 90.0, epsilon = 0.5e-5); 2350 | assert_relative_eq!(s12, 19994492.0, epsilon = 0.5); 2351 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 180.0); 2352 | assert_relative_eq!(azi1, 90.0, epsilon = 0.5e-5); 2353 | assert_relative_eq!(azi2, 90.0, epsilon = 0.5e-5); 2354 | assert_relative_eq!(s12, 20106193.0, epsilon = 0.5); 2355 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.5, 180.0); 2356 | assert_relative_eq!(azi1, 33.02493, epsilon = 0.5e-5); 2357 | assert_relative_eq!(azi2, 146.97364, epsilon = 0.5e-5); 2358 | assert_relative_eq!(s12, 20082617.0, epsilon = 0.5); 2359 | let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 1.0, 180.0); 2360 | assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5); 2361 | assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5); 2362 | assert_relative_eq!(s12, 20027270.0, epsilon = 0.5); 2363 | } 2364 | 2365 | #[test] 2366 | fn test_std_geodesic_geodsolve55() { 2367 | // Check fix for nan + point on equator or pole not returning all nans in 2368 | // Geodesic::Inverse, found 2015-09-23. 2369 | let geod = Geodesic::wgs84(); 2370 | let (s12, azi1, azi2, _a12) = geod.inverse(f64::NAN, 0.0, 0.0, 90.0); 2371 | assert!(azi1.is_nan()); 2372 | assert!(azi2.is_nan()); 2373 | assert!(s12.is_nan()); 2374 | let (s12, azi1, azi2, _a12) = geod.inverse(f64::NAN, 0.0, 90.0, 3.0); 2375 | assert!(azi1.is_nan()); 2376 | assert!(azi2.is_nan()); 2377 | assert!(s12.is_nan()); 2378 | } 2379 | 2380 | #[test] 2381 | fn test_std_geodesic_geodsolve59() { 2382 | // Check for points close with longitudes close to 180 deg apart. 2383 | let geod = Geodesic::wgs84(); 2384 | let (s12, azi1, azi2, _a12) = geod.inverse(5.0, 0.00000000000001, 10.0, 180.0); 2385 | assert_relative_eq!(azi1, 0.000000000000035, epsilon = 1.5e-14); 2386 | assert_relative_eq!(azi2, 179.99999999999996, epsilon = 1.5e-14); 2387 | assert_relative_eq!(s12, 18345191.174332713, epsilon = 5e-9); 2388 | } 2389 | 2390 | #[test] 2391 | fn test_std_geodesic_geodsolve61() { 2392 | // Make sure small negative azimuths are west-going 2393 | let geod = Geodesic::wgs84(); 2394 | let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) = geod._gen_direct( 2395 | 45.0, 2396 | 0.0, 2397 | -0.000000000000000003, 2398 | false, 2399 | 1e7, 2400 | caps::STANDARD | caps::LONG_UNROLL, 2401 | ); 2402 | assert_relative_eq!(lat2, 45.30632, epsilon = 0.5e-5); 2403 | assert_relative_eq!(lon2, -180.0, epsilon = 0.5e-5); 2404 | assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5); 2405 | // geographiclib-rs does not appear to support Geodesic.inverse_line or 2406 | // or GeodesicLine.position as of 2021/01/18. 2407 | // let line = geod.inverse_line(45, 0, 80, -0.000000000000000003); 2408 | // let res = line.position(1e7, caps::STANDARD | caps::LONG_UNROLL); 2409 | // assert_relative_eq!(lat2, 45.30632, epsilon = 0.5e-5); 2410 | // assert_relative_eq!(lon2, -180, epsilon = 0.5e-5); 2411 | // assert_relative_eq!(azi2.abs(), 180, epsilon = 0.5e-5); 2412 | } 2413 | 2414 | // #[test] 2415 | // fn test_std_geodesic_geodsolve65() { 2416 | // // Check for bug in east-going check in GeodesicLine (needed to check for 2417 | // // sign of 0) and sign error in area calculation due to a bogus override 2418 | // // of the code for alp12. Found/fixed on 2015-12-19. 2419 | // // These tests rely on Geodesic.inverse_line, which is not supported by 2420 | // // geographiclib-rs as of 2021/01/18. 2421 | // } 2422 | 2423 | // #[test] 2424 | // fn test_std_geodesic_geodsolve69() { 2425 | // // Check for InverseLine if line is slightly west of S and that s13 is 2426 | // // correctly set. 2427 | // // These tests rely on Geodesic.inverse_line, which is not supported by 2428 | // // geographiclib-rs as of 2021/01/18. 2429 | // } 2430 | 2431 | // #[test] 2432 | // fn test_std_geodesic_geodsolve71() { 2433 | // // Check that DirectLine sets s13. 2434 | // // These tests rely on Geodesic.direct_line, which is not supported by 2435 | // // geographiclib-rs as of 2021/01/18. 2436 | // } 2437 | 2438 | #[test] 2439 | fn test_std_geodesic_geodsolve73() { 2440 | // Check for backwards from the pole bug reported by Anon on 2016-02-13. 2441 | // This only affected the Java implementation. It was introduced in Java 2442 | // version 1.44 and fixed in 1.46-SNAPSHOT on 2016-01-17. 2443 | // Also the + sign on azi2 is a check on the normalizing of azimuths 2444 | // (converting -0.0 to +0.0). 2445 | let geod = Geodesic::wgs84(); 2446 | let (lat2, lon2, azi2) = geod.direct(90.0, 10.0, 180.0, -1e6); 2447 | assert_relative_eq!(lat2, 81.04623, epsilon = 0.5e-5); 2448 | assert_relative_eq!(lon2, -170.0, epsilon = 0.5e-5); 2449 | assert_relative_eq!(azi2, 0.0, epsilon = 0.5e-5); 2450 | assert!(azi2.is_sign_positive()); 2451 | } 2452 | 2453 | #[test] 2454 | fn test_std_geodesic_geodsolve74() { 2455 | // Check fix for inaccurate areas, bug introduced in v1.46, fixed 2456 | // 2015-10-16. 2457 | let geod = Geodesic::wgs84(); 2458 | let (a12, s12, azi1, azi2, m12, M12, M21, S12) = 2459 | geod._gen_inverse_azi(54.1589, 15.3872, 54.1591, 15.3877, caps::ALL); 2460 | assert_relative_eq!(azi1, 55.723110355, epsilon = 5e-9); 2461 | assert_relative_eq!(azi2, 55.723515675, epsilon = 5e-9); 2462 | assert_relative_eq!(s12, 39.527686385, epsilon = 5e-9); 2463 | assert_relative_eq!(a12, 0.000355495, epsilon = 5e-9); 2464 | assert_relative_eq!(m12, 39.527686385, epsilon = 5e-9); 2465 | assert_relative_eq!(M12, 0.999999995, epsilon = 5e-9); 2466 | assert_relative_eq!(M21, 0.999999995, epsilon = 5e-9); 2467 | assert_relative_eq!(S12, 286698586.30197, epsilon = 5e-4); 2468 | } 2469 | 2470 | #[test] 2471 | fn test_std_geodesic_geodsolve76() { 2472 | // The distance from Wellington and Salamanca (a classic failure of 2473 | // Vincenty) 2474 | let geod = Geodesic::wgs84(); 2475 | let (s12, azi1, azi2, _a12) = geod.inverse( 2476 | -(41.0 + 19.0 / 60.0), 2477 | 174.0 + 49.0 / 60.0, 2478 | 40.0 + 58.0 / 60.0, 2479 | -(5.0 + 30.0 / 60.0), 2480 | ); 2481 | assert_relative_eq!(azi1, 160.39137649664, epsilon = 0.5e-11); 2482 | assert_relative_eq!(azi2, 19.50042925176, epsilon = 0.5e-11); 2483 | assert_relative_eq!(s12, 19960543.857179, epsilon = 0.5e-6); 2484 | } 2485 | 2486 | #[test] 2487 | fn test_std_geodesic_geodsolve78() { 2488 | // An example where the NGS calculator fails to converge 2489 | let geod = Geodesic::wgs84(); 2490 | let (s12, azi1, azi2, _a12) = geod.inverse(27.2, 0.0, -27.1, 179.5); 2491 | assert_relative_eq!(azi1, 45.82468716758, epsilon = 0.5e-11); 2492 | assert_relative_eq!(azi2, 134.22776532670, epsilon = 0.5e-11); 2493 | assert_relative_eq!(s12, 19974354.765767, epsilon = 0.5e-6); 2494 | } 2495 | 2496 | #[test] 2497 | fn test_std_geodesic_geodsolve80() { 2498 | // Some tests to add code coverage: computing scale in special cases + zero 2499 | // length geodesic (includes GeodSolve80 - GeodSolve83). 2500 | let geod = Geodesic::wgs84(); 2501 | let (_a12, _s12, _salp1, _calp1, _salp2, _calp2, _m12, M12, M21, _S12) = 2502 | geod._gen_inverse(0.0, 0.0, 0.0, 90.0, caps::GEODESICSCALE); 2503 | assert_relative_eq!(M12, -0.00528427534, epsilon = 0.5e-10); 2504 | assert_relative_eq!(M21, -0.00528427534, epsilon = 0.5e-10); 2505 | 2506 | let (_a12, _s12, _salp1, _calp1, _salp2, _calp2, _m12, M12, M21, _S12) = 2507 | geod._gen_inverse(0.0, 0.0, 1e-6, 1e-6, caps::GEODESICSCALE); 2508 | assert_relative_eq!(M12, 1.0, epsilon = 0.5e-10); 2509 | assert_relative_eq!(M21, 1.0, epsilon = 0.5e-10); 2510 | 2511 | let (a12, s12, azi1, azi2, m12, M12, M21, S12) = 2512 | geod._gen_inverse_azi(20.001, 0.0, 20.001, 0.0, caps::ALL); 2513 | assert_relative_eq!(a12, 0.0, epsilon = 1e-13); 2514 | assert_relative_eq!(s12, 0.0, epsilon = 1e-8); 2515 | assert_relative_eq!(azi1, 180.0, epsilon = 1e-13); 2516 | assert_relative_eq!(azi2, 180.0, epsilon = 1e-13); 2517 | assert_relative_eq!(m12, 0.0, epsilon = 1e-8); 2518 | assert_relative_eq!(M12, 1.0, epsilon = 1e-15); 2519 | assert_relative_eq!(M21, 1.0, epsilon = 1e-15); 2520 | assert_relative_eq!(S12, 0.0, epsilon = 1e-10); 2521 | 2522 | let (a12, s12, azi1, azi2, m12, M12, M21, S12) = 2523 | geod._gen_inverse_azi(90.0, 0.0, 90.0, 180.0, caps::ALL); 2524 | assert_relative_eq!(a12, 0.0, epsilon = 1e-13); 2525 | assert_relative_eq!(s12, 0.0, epsilon = 1e-8); 2526 | assert_relative_eq!(azi1, 0.0, epsilon = 1e-13); 2527 | assert_relative_eq!(azi2, 180.0, epsilon = 1e-13); 2528 | assert_relative_eq!(m12, 0.0, epsilon = 1e-8); 2529 | assert_relative_eq!(M12, 1.0, epsilon = 1e-15); 2530 | assert_relative_eq!(M21, 1.0, epsilon = 1e-15); 2531 | assert_relative_eq!(S12, 127516405431022.0, epsilon = 0.5); 2532 | 2533 | // An incapable line which can't take distance as input 2534 | let line = GeodesicLine::new(&geod, 1.0, 2.0, 90.0, Some(caps::LATITUDE), None, None); 2535 | let (a12, _lat2, _lon2, _azi2, _s12, _m12, _M12, _M21, _S12) = 2536 | line._gen_position(false, 1000.0, caps::CAP_NONE); 2537 | assert!(a12.is_nan()); 2538 | } 2539 | 2540 | #[test] 2541 | fn test_std_geodesic_geodsolve84() { 2542 | // Tests for python implementation to check fix for range errors with 2543 | // {fmod,sin,cos}(inf) (includes GeodSolve84 - GeodSolve91). 2544 | let geod = Geodesic::wgs84(); 2545 | let (lat2, lon2, azi2) = geod.direct(0.0, 0.0, 90.0, f64::INFINITY); 2546 | assert!(lat2.is_nan()); 2547 | assert!(lon2.is_nan()); 2548 | assert!(azi2.is_nan()); 2549 | let (lat2, lon2, azi2) = geod.direct(0.0, 0.0, 90.0, f64::NAN); 2550 | assert!(lat2.is_nan()); 2551 | assert!(lon2.is_nan()); 2552 | assert!(azi2.is_nan()); 2553 | let (lat2, lon2, azi2) = geod.direct(0.0, 0.0, f64::INFINITY, 1000.0); 2554 | assert!(lat2.is_nan()); 2555 | assert!(lon2.is_nan()); 2556 | assert!(azi2.is_nan()); 2557 | let (lat2, lon2, azi2) = geod.direct(0.0, 0.0, f64::NAN, 1000.0); 2558 | assert!(lat2.is_nan()); 2559 | assert!(lon2.is_nan()); 2560 | assert!(azi2.is_nan()); 2561 | let (lat2, lon2, azi2) = geod.direct(0.0, f64::INFINITY, 90.0, 1000.0); 2562 | assert_eq!(lat2, 0.0); 2563 | assert!(lon2.is_nan()); 2564 | assert_eq!(azi2, 90.0); 2565 | let (lat2, lon2, azi2) = geod.direct(0.0, f64::NAN, 90.0, 1000.0); 2566 | assert_eq!(lat2, 0.0); 2567 | assert!(lon2.is_nan()); 2568 | assert_eq!(azi2, 90.0); 2569 | let (lat2, lon2, azi2) = geod.direct(f64::INFINITY, 0.0, 90.0, 1000.0); 2570 | assert!(lat2.is_nan()); 2571 | assert!(lon2.is_nan()); 2572 | assert!(azi2.is_nan()); 2573 | let (lat2, lon2, azi2) = geod.direct(f64::NAN, 0.0, 90.0, 1000.0); 2574 | assert!(lat2.is_nan()); 2575 | assert!(lon2.is_nan()); 2576 | assert!(azi2.is_nan()); 2577 | } 2578 | 2579 | // *_geodtest_* tests are based on Karney's GeodTest*.dat test datasets. 2580 | // A description of these files' content can be found at: 2581 | // https://geographiclib.sourceforge.io/html/geodesic.html#testgeod 2582 | // Here are some key excerpts... 2583 | // This consists of a set of geodesics for the WGS84 ellipsoid. 2584 | // Each line of the test set gives 10 space delimited numbers 2585 | // latitude at point 1, lat1 (degrees, exact) 2586 | // longitude at point 1, lon1 (degrees, always 0) 2587 | // azimuth at point 1, azi1 (clockwise from north in degrees, exact) 2588 | // latitude at point 2, lat2 (degrees, accurate to 10−18 deg) 2589 | // longitude at point 2, lon2 (degrees, accurate to 10−18 deg) 2590 | // azimuth at point 2, azi2 (degrees, accurate to 10−18 deg) 2591 | // geodesic distance from point 1 to point 2, s12 (meters, exact) 2592 | // arc distance on the auxiliary sphere, a12 (degrees, accurate to 10−18 deg) 2593 | // reduced length of the geodesic, m12 (meters, accurate to 0.1 pm) 2594 | // the area under the geodesic, S12 (m2, accurate to 1 mm2) 2595 | 2596 | static FULL_TEST_PATH: &str = "test_fixtures/test_data_unzipped/GeodTest.dat"; 2597 | static SHORT_TEST_PATH: &str = "test_fixtures/test_data_unzipped/GeodTest-short.dat"; 2598 | static BUILTIN_TEST_PATH: &str = "test_fixtures/GeodTest-100.dat"; 2599 | fn test_input_path() -> &'static str { 2600 | if cfg!(feature = "test_full") { 2601 | FULL_TEST_PATH 2602 | } else if cfg!(feature = "test_short") { 2603 | SHORT_TEST_PATH 2604 | } else { 2605 | BUILTIN_TEST_PATH 2606 | } 2607 | } 2608 | 2609 | fn geodtest_basic(path: &str, f: T) 2610 | where 2611 | T: Fn(usize, &(f64, f64, f64, f64, f64, f64, f64, f64, f64, f64)), 2612 | { 2613 | let dir_base = std::env::current_dir().expect("Failed to determine current directory"); 2614 | let path_base = dir_base.as_path(); 2615 | let pathbuf = std::path::Path::new(path_base).join(path); 2616 | let path = pathbuf.as_path(); 2617 | let file = match std::fs::File::open(path) { 2618 | Ok(val) => val, 2619 | Err(_error) => { 2620 | let path_str = path 2621 | .to_str() 2622 | .expect("Failed to convert GeodTest path to string during error reporting"); 2623 | panic!("Failed to open test input file. Run `script/download-test-data.sh` to download test input to: {}\nFor details see https://geographiclib.sourceforge.io/html/geodesic.html#testgeod", path_str) 2624 | } 2625 | }; 2626 | let reader = std::io::BufReader::new(file); 2627 | reader.lines().enumerate().for_each(|(i, line)| { 2628 | let line_safe = line.expect("Failed to read line"); 2629 | let items: Vec = line_safe 2630 | .split(' ') 2631 | .enumerate() 2632 | .map(|(j, item)| match item.parse::() { 2633 | Ok(parsed) => parsed, 2634 | Err(_error) => { 2635 | panic!("Error parsing item {} on line {}: {}", j + 1, i + 1, item) 2636 | } 2637 | }) 2638 | .collect(); 2639 | assert_eq!(items.len(), 10); 2640 | let tuple = ( 2641 | items[0], items[1], items[2], items[3], items[4], items[5], items[6], items[7], 2642 | items[8], items[9], 2643 | ); 2644 | f(i + 1, &tuple); // report 1-based line number rather than 0-based 2645 | }); 2646 | } 2647 | 2648 | #[test] 2649 | fn test_geodtest_geodesic_direct12() { 2650 | let g = std::sync::Arc::new(std::sync::Mutex::new(Geodesic::wgs84())); 2651 | 2652 | geodtest_basic( 2653 | test_input_path(), 2654 | |_line_num, &(lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, S12)| { 2655 | let g = g.lock().unwrap(); 2656 | let (lat2_out, lon2_out, azi2_out, m12_out, _M12_out, _M21_out, S12_out, a12_out) = 2657 | g.direct(lat1, lon1, azi1, s12); 2658 | assert_relative_eq!(lat2, lat2_out, epsilon = 1e-13); 2659 | assert_relative_eq!(lon2, lon2_out, epsilon = 2e-8); 2660 | assert_relative_eq!(azi2, azi2_out, epsilon = 2e-8); 2661 | assert_relative_eq!(m12, m12_out, epsilon = 9e-9); 2662 | assert_relative_eq!(S12, S12_out, epsilon = 2e4); // Note: unreasonable tolerance 2663 | assert_relative_eq!(a12, a12_out, epsilon = 9e-14); 2664 | }, 2665 | ); 2666 | } 2667 | 2668 | #[test] 2669 | fn test_geodtest_geodesic_direct21() { 2670 | let g = std::sync::Arc::new(std::sync::Mutex::new(Geodesic::wgs84())); 2671 | 2672 | geodtest_basic( 2673 | test_input_path(), 2674 | |_line_num, &(lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, S12)| { 2675 | let g = g.lock().unwrap(); 2676 | // Reverse some values for 2->1 instead of 1->2 2677 | let (lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, S12) = 2678 | (lat2, lon2, azi2, lat1, lon1, azi1, -s12, -a12, -m12, -S12); 2679 | let (lat2_out, lon2_out, azi2_out, m12_out, _M12_out, _M21_out, S12_out, a12_out) = 2680 | g.direct(lat1, lon1, azi1, s12); 2681 | assert_relative_eq!(lat2, lat2_out, epsilon = 8e-14); 2682 | assert_relative_eq!(lon2, lon2_out, epsilon = 4e-6); 2683 | assert_relative_eq!(azi2, azi2_out, epsilon = 4e-6); 2684 | assert_relative_eq!(m12, m12_out, epsilon = 1e-8); 2685 | assert_relative_eq!(S12, S12_out, epsilon = 3e6); // Note: unreasonable tolerance 2686 | assert_relative_eq!(a12, a12_out, epsilon = 9e-14); 2687 | }, 2688 | ); 2689 | } 2690 | 2691 | #[test] 2692 | fn test_geodtest_geodesic_inverse12() { 2693 | let g = std::sync::Arc::new(std::sync::Mutex::new(Geodesic::wgs84())); 2694 | 2695 | geodtest_basic( 2696 | test_input_path(), 2697 | |line_num, &(lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, S12)| { 2698 | let g = g.lock().unwrap(); 2699 | let (s12_out, azi1_out, azi2_out, m12_out, _M12_out, _M21_out, S12_out, a12_out) = 2700 | g.inverse(lat1, lon1, lat2, lon2); 2701 | assert_relative_eq!(s12, s12_out, epsilon = 8e-9); 2702 | assert_relative_eq!(azi1, azi1_out, epsilon = 2e-2); 2703 | assert_relative_eq!(azi2, azi2_out, epsilon = 2e-2); 2704 | assert_relative_eq!(m12, m12_out, epsilon = 5e-5); 2705 | // Our area calculation differs significantly (~1e7) from the value in GeodTest.dat for 2706 | // line 400001, BUT our value also perfectly matches the value returned by GeographicLib 2707 | // (C++) 1.51. Here's the problem line, for reference: 2708 | // 4.199535552987 0 90 -4.199535552987 179.398106343454992238 90 19970505.608097404994 180 0 2709 | if line_num != 400001 { 2710 | assert_relative_eq!(S12, S12_out, epsilon = 3e10); // Note: unreasonable tolerance 2711 | } 2712 | assert_relative_eq!(a12, a12_out, epsilon = 2e-10); 2713 | }, 2714 | ); 2715 | } 2716 | 2717 | #[test] 2718 | fn test_turnaround() { 2719 | let g = Geodesic::wgs84(); 2720 | 2721 | let start = (0.0, 0.0); 2722 | let destination = (0.0, 1.0); 2723 | 2724 | let (distance, azi1, _, _) = g.inverse(start.0, start.1, destination.0, destination.1); 2725 | 2726 | // Confirm that we've gone due-east 2727 | assert_eq!(azi1, 90.0); 2728 | 2729 | // Turn around by adding 180 degrees to the azimuth 2730 | let turn_around = azi1 + 180.0; 2731 | 2732 | // Confirm that turn around is due west 2733 | assert_eq!(turn_around, 270.0); 2734 | 2735 | // Test that we can turn around and get back to the starting point. 2736 | let (lat, lon) = g.direct(destination.0, destination.1, turn_around, distance); 2737 | assert_relative_eq!(lat, start.0, epsilon = 1.0e-3); 2738 | assert_relative_eq!(lon, start.1, epsilon = 1.0e-3); 2739 | } 2740 | } 2741 | -------------------------------------------------------------------------------- /src/geodesic_capability.rs: -------------------------------------------------------------------------------- 1 | pub const CAP_NONE: u64 = 0; 2 | pub const CAP_C1: u64 = 1 << 0; 3 | #[allow(non_upper_case_globals)] 4 | pub const CAP_C1p: u64 = 1 << 1; 5 | pub const CAP_C2: u64 = 1 << 2; 6 | pub const CAP_C3: u64 = 1 << 3; 7 | pub const CAP_C4: u64 = 1 << 4; 8 | pub const CAP_ALL: u64 = 0x1F; 9 | pub const CAP_MASK: u64 = CAP_ALL; 10 | pub const OUT_ALL: u64 = 0x7F80; 11 | // Includes LONG_UNROLL 12 | pub const OUT_MASK: u64 = 0xFF80; 13 | pub const EMPTY: u64 = 0; 14 | pub const LATITUDE: u64 = (1 << 7) | CAP_NONE; 15 | pub const LONGITUDE: u64 = (1 << 8) | CAP_C3; 16 | pub const AZIMUTH: u64 = (1 << 9) | CAP_NONE; 17 | pub const DISTANCE: u64 = (1 << 10) | CAP_C1; 18 | pub const STANDARD: u64 = LATITUDE | LONGITUDE | AZIMUTH | DISTANCE; 19 | pub const DISTANCE_IN: u64 = (1 << 11) | CAP_C1 | CAP_C1p; 20 | pub const REDUCEDLENGTH: u64 = (1 << 12) | CAP_C1 | CAP_C2; 21 | pub const GEODESICSCALE: u64 = (1 << 13) | CAP_C1 | CAP_C2; 22 | pub const AREA: u64 = (1 << 14) | CAP_C4; 23 | pub const LONG_UNROLL: u64 = 1 << 15; 24 | // Does not include LONG_UNROLL 25 | pub const ALL: u64 = OUT_ALL | CAP_ALL; 26 | -------------------------------------------------------------------------------- /src/geodesic_line.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use crate::geodesic::{self, GEODESIC_ORDER}; 4 | use crate::geodesic_capability as caps; 5 | use crate::geomath; 6 | use std::collections::HashMap; 7 | 8 | /// A geodesic line. 9 | /// 10 | /// Facilitates the determination of a series of points on a single geodesic. 11 | #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] 12 | pub struct GeodesicLine { 13 | tiny_: f64, // This should be moved to consts 14 | _A1m1: f64, 15 | _A2m1: f64, 16 | _A3c: f64, 17 | _A4: f64, 18 | _B11: f64, 19 | _B21: f64, 20 | _B31: f64, 21 | _B41: f64, 22 | _C1a: [f64; GEODESIC_ORDER + 1], 23 | _C1pa: [f64; GEODESIC_ORDER + 1], 24 | _C2a: [f64; GEODESIC_ORDER + 1], 25 | _C3a: [f64; GEODESIC_ORDER], 26 | _C4a: [f64; GEODESIC_ORDER], 27 | _b: f64, 28 | _c2: f64, 29 | _calp0: f64, 30 | _csig1: f64, 31 | _comg1: f64, 32 | _ctau1: f64, 33 | _dn1: f64, 34 | _f1: f64, 35 | _k2: f64, 36 | _salp0: f64, 37 | _somg1: f64, 38 | _ssig1: f64, 39 | _stau1: f64, 40 | _a13: f64, 41 | _a: f64, 42 | azi1: f64, 43 | calp1: f64, 44 | caps: u64, 45 | f: f64, 46 | lat1: f64, 47 | lon1: f64, 48 | _s13: f64, 49 | salp1: f64, 50 | } 51 | 52 | impl GeodesicLine { 53 | /// Create a new geodesic line from an initial point and azimuth. 54 | /// 55 | /// # Arguments 56 | /// - geod - geodesic parameters 57 | /// - lat1 - initial latitude (degrees) 58 | /// - lon1 - initial longitude (degrees) 59 | /// - azi1 - initial azimuth (degrees) 60 | /// - caps - bitor'ed combination of [capabilities](caps); defaults to `STANDARD | DISTANCE_IN` 61 | /// - `caps |= LATITUDE` for the latitude lat2; this is added automatically 62 | /// - `caps |= LONGITUDE` for the longitude lon2 63 | /// - `caps |= AZIMUTH` for the azimuth azi2; this is added automatically 64 | /// - `caps |= DISTANCE` for the distance s12 65 | /// - `caps |= REDUCEDLENGTH` for the reduced length m12 66 | /// - `caps |= GEODESICSCALE` for the geodesic scales M12 and M21 67 | /// - `caps |= AREA` for the area S12 68 | /// - `caps |= DISTANCE_IN` permits the length of the geodesic to be given in terms of s12; 69 | /// without this capability the length can only be specified in terms of arc length 70 | /// - `caps |= ALL` for all of the above 71 | /// - salp1 - sine of initial azimuth 72 | /// - calp1 - cosine of initial azimuth 73 | pub fn new( 74 | geod: &geodesic::Geodesic, 75 | lat1: f64, 76 | lon1: f64, 77 | azi1: f64, 78 | caps: Option, 79 | salp1: Option, 80 | calp1: Option, 81 | ) -> Self { 82 | let caps = match caps { 83 | None => caps::STANDARD | caps::DISTANCE_IN, 84 | Some(caps) => caps, 85 | }; 86 | let salp1 = match salp1 { 87 | None => f64::NAN, 88 | Some(salp1) => salp1, 89 | }; 90 | let calp1 = match calp1 { 91 | None => f64::NAN, 92 | Some(calp1) => calp1, 93 | }; 94 | 95 | // This was taken from geodesic, putting it here for convenience 96 | let tiny_ = geomath::get_min_val().sqrt(); 97 | 98 | let _a = geod.a; 99 | let f = geod.f; 100 | let _b = geod._b; 101 | let _c2 = geod._c2; 102 | let _f1 = geod._f1; 103 | let caps = caps | caps::LATITUDE | caps::AZIMUTH | caps::LONG_UNROLL; 104 | let (azi1, salp1, calp1) = if salp1.is_nan() || calp1.is_nan() { 105 | let azi1 = geomath::ang_normalize(azi1); 106 | let (salp1, calp1) = geomath::sincosd(geomath::ang_round(azi1)); 107 | (azi1, salp1, calp1) 108 | } else { 109 | (azi1, salp1, calp1) 110 | }; 111 | let lat1 = geomath::lat_fix(lat1); 112 | 113 | let (mut sbet1, mut cbet1) = geomath::sincosd(geomath::ang_round(lat1)); 114 | sbet1 *= _f1; 115 | geomath::norm(&mut sbet1, &mut cbet1); 116 | cbet1 = tiny_.max(cbet1); 117 | let _dn1 = (1.0 + geod._ep2 * geomath::sq(sbet1)).sqrt(); 118 | let _salp0 = salp1 * cbet1; 119 | let _calp0 = calp1.hypot(salp1 * sbet1); 120 | let mut _ssig1 = sbet1; 121 | let _somg1 = _salp0 * sbet1; 122 | let mut _csig1 = if sbet1 != 0.0 || calp1 != 0.0 { 123 | cbet1 * calp1 124 | } else { 125 | 1.0 126 | }; 127 | let _comg1 = _csig1; 128 | geomath::norm(&mut _ssig1, &mut _csig1); 129 | let _k2 = geomath::sq(_calp0) * geod._ep2; 130 | let eps = _k2 / (2.0 * (1.0 + (1.0 + _k2).sqrt()) + _k2); 131 | 132 | let mut _A1m1 = 0.0; 133 | let mut _C1a: [f64; GEODESIC_ORDER + 1] = [0.0; GEODESIC_ORDER + 1]; 134 | let mut _B11 = 0.0; 135 | let mut _stau1 = 0.0; 136 | let mut _ctau1 = 0.0; 137 | if caps & caps::CAP_C1 != 0 { 138 | _A1m1 = geomath::_A1m1f(eps, geod.GEODESIC_ORDER); 139 | geomath::_C1f(eps, &mut _C1a, geod.GEODESIC_ORDER); 140 | _B11 = geomath::sin_cos_series(true, _ssig1, _csig1, &_C1a); 141 | let s = _B11.sin(); 142 | let c = _B11.cos(); 143 | _stau1 = _ssig1 * c + _csig1 * s; 144 | _ctau1 = _csig1 * c - _ssig1 * s; 145 | } 146 | 147 | let mut _C1pa: [f64; GEODESIC_ORDER + 1] = [0.0; GEODESIC_ORDER + 1]; 148 | if caps & caps::CAP_C1p != 0 { 149 | geomath::_C1pf(eps, &mut _C1pa, geod.GEODESIC_ORDER); 150 | } 151 | 152 | let mut _A2m1 = 0.0; 153 | let mut _C2a: [f64; GEODESIC_ORDER + 1] = [0.0; GEODESIC_ORDER + 1]; 154 | let mut _B21 = 0.0; 155 | if caps & caps::CAP_C2 != 0 { 156 | _A2m1 = geomath::_A2m1f(eps, geod.GEODESIC_ORDER); 157 | geomath::_C2f(eps, &mut _C2a, geod.GEODESIC_ORDER); 158 | _B21 = geomath::sin_cos_series(true, _ssig1, _csig1, &_C2a); 159 | } 160 | 161 | let mut _C3a: [f64; GEODESIC_ORDER] = [0.0; GEODESIC_ORDER]; 162 | let mut _A3c = 0.0; 163 | let mut _B31 = 0.0; 164 | if caps & caps::CAP_C3 != 0 { 165 | geod._C3f(eps, &mut _C3a); 166 | _A3c = -f * _salp0 * geod._A3f(eps); 167 | _B31 = geomath::sin_cos_series(true, _ssig1, _csig1, &_C3a); 168 | } 169 | 170 | let mut _C4a: [f64; GEODESIC_ORDER] = [0.0; GEODESIC_ORDER]; 171 | let mut _A4 = 0.0; 172 | let mut _B41 = 0.0; 173 | if caps & caps::CAP_C4 != 0 { 174 | geod._C4f(eps, &mut _C4a); 175 | _A4 = geomath::sq(_a) * _calp0 * _salp0 * geod._e2; 176 | _B41 = geomath::sin_cos_series(false, _ssig1, _csig1, &_C4a); 177 | } 178 | 179 | let _s13 = f64::NAN; 180 | let _a13 = f64::NAN; 181 | 182 | GeodesicLine { 183 | tiny_, 184 | _A1m1, 185 | _A2m1, 186 | _A3c, 187 | _A4, 188 | _B11, 189 | _B21, 190 | _B31, 191 | _B41, 192 | _C1a, 193 | _C1pa, 194 | _comg1, 195 | _C2a, 196 | _C3a, 197 | _C4a, 198 | _b, 199 | _c2, 200 | _calp0, 201 | _csig1, 202 | _ctau1, 203 | _dn1, 204 | _f1, 205 | _k2, 206 | _salp0, 207 | _somg1, 208 | _ssig1, 209 | _stau1, 210 | _a, 211 | _a13, 212 | azi1, 213 | calp1, 214 | caps, 215 | f, 216 | lat1, 217 | lon1, 218 | _s13, 219 | salp1, 220 | } 221 | } 222 | 223 | /// Place a point at a given distance along this line. 224 | /// 225 | /// # Arguments 226 | /// - arcmode - Indicates if s12_a12 is an arc length (true) or distance (false) 227 | /// - s12_a12 - Distance to point (meters) or arc length (degrees); can be negative 228 | /// - outmask - bitor'ed combination of [capabilities](caps) defining which values to return 229 | /// - `outmask |= LATITUDE` for the latitude lat2 230 | /// - `outmask |= LONGITUDE` for the longitude lon2 231 | /// - `outmask |= AZIMUTH` for the azimuth azi2 232 | /// - `outmask |= DISTANCE` for the distance s12 233 | /// - `outmask |= REDUCEDLENGTH` for the reduced length m12 234 | /// - `outmask |= GEODESICSCALE` for the geodesic scales M12 and M21 235 | /// - `outmask |= ALL` for all of the above 236 | /// - `outmask |= LONG_UNROLL` to unroll lon2 (instead of reducing it to the range [−180°, 180°]) 237 | /// 238 | /// # Returns 239 | /// Values are [f64::NAN] if not supported by the specified capabilities. 240 | /// - a12 arc length between point 1 and point 2 (degrees) 241 | /// - lat2 latitude of point 2 (degrees) 242 | /// - lon2 longitude of point 2 (degrees) 243 | /// - azi2 (forward) azimuth at point 2 (degrees) 244 | /// - s12 distance between point 1 and point 2 (meters) 245 | /// - m12 reduced length of geodesic (meters) 246 | /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless) 247 | /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless) 248 | /// - S12 area under the geodesic (meters2) 249 | /// 250 | /// # Example 251 | /// ```rust 252 | /// use geographiclib_rs::{Geodesic, GeodesicLine, geodesic_capability as caps}; 253 | /// let g = Geodesic::wgs84(); 254 | /// let outmask = caps::LATITUDE | caps::LONGITUDE | caps::AZIMUTH | caps::DISTANCE_IN; 255 | /// let line = GeodesicLine::new(&g, -11.95887, -116.94513, 92.712619830452549, Some(outmask), None, None); 256 | /// let (_, lat2, lon2, azi2, _, _, _, _, _) = line._gen_position(false, 13834722.5801401374, outmask); 257 | /// 258 | /// use approx::assert_relative_eq; 259 | /// assert_relative_eq!(lat2, 4.57352, epsilon=1e-13); 260 | /// assert_relative_eq!(lon2, 7.16501, epsilon=1e-13); 261 | /// assert_relative_eq!(azi2, 78.64960934409585, epsilon=1e-13); 262 | /// ``` 263 | pub fn _gen_position( 264 | &self, 265 | arcmode: bool, 266 | s12_a12: f64, 267 | outmask: u64, 268 | ) -> (f64, f64, f64, f64, f64, f64, f64, f64, f64) { 269 | let mut a12 = f64::NAN; 270 | let mut lat2 = f64::NAN; 271 | let mut lon2 = f64::NAN; 272 | let mut azi2 = f64::NAN; 273 | let mut s12 = f64::NAN; 274 | let mut m12 = f64::NAN; 275 | let mut M12 = f64::NAN; 276 | let mut M21 = f64::NAN; 277 | let mut S12 = f64::NAN; 278 | let outmask = outmask & (self.caps & caps::OUT_MASK); 279 | if !(arcmode || (self.caps & (caps::OUT_MASK & caps::DISTANCE_IN) != 0)) { 280 | return (a12, lat2, lon2, azi2, s12, m12, M12, M21, S12); 281 | } 282 | 283 | let mut B12 = 0.0; 284 | let mut AB1 = 0.0; 285 | let mut sig12: f64; 286 | let mut ssig12: f64; 287 | let mut csig12: f64; 288 | let mut ssig2: f64; 289 | let mut csig2: f64; 290 | if arcmode { 291 | sig12 = s12_a12.to_radians(); 292 | let res = geomath::sincosd(s12_a12); 293 | ssig12 = res.0; 294 | csig12 = res.1; 295 | } else { 296 | // tau12 = s12_a12 / (self._b * (1 + self._A1m1)) 297 | let tau12 = s12_a12 / (self._b * (1.0 + self._A1m1)); 298 | 299 | let s = tau12.sin(); 300 | let c = tau12.cos(); 301 | 302 | B12 = -geomath::sin_cos_series( 303 | true, 304 | self._stau1 * c + self._ctau1 * s, 305 | self._ctau1 * c - self._stau1 * s, 306 | &self._C1pa, 307 | ); 308 | sig12 = tau12 - (B12 - self._B11); 309 | ssig12 = sig12.sin(); 310 | csig12 = sig12.cos(); 311 | if self.f.abs() > 0.01 { 312 | ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12; 313 | csig2 = self._csig1 * csig12 - self._ssig1 * ssig12; 314 | B12 = geomath::sin_cos_series(true, ssig2, csig2, &self._C1a); 315 | let serr = (1.0 + self._A1m1) * (sig12 + (B12 - self._B11)) - s12_a12 / self._b; 316 | sig12 -= serr / (1.0 + self._k2 * geomath::sq(ssig2)).sqrt(); 317 | ssig12 = sig12.sin(); 318 | csig12 = sig12.cos(); 319 | } 320 | }; 321 | ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12; 322 | csig2 = self._csig1 * csig12 - self._ssig1 * ssig12; 323 | let dn2 = (1.0 + self._k2 * geomath::sq(ssig2)).sqrt(); 324 | if outmask & (caps::DISTANCE | caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 { 325 | if arcmode || self.f.abs() > 0.01 { 326 | B12 = geomath::sin_cos_series(true, ssig2, csig2, &self._C1a); 327 | } 328 | AB1 = (1.0 + self._A1m1) * (B12 - self._B11); 329 | } 330 | 331 | let sbet2 = self._calp0 * ssig2; 332 | let mut cbet2 = self._salp0.hypot(self._calp0 * csig2); 333 | if cbet2 == 0.0 { 334 | cbet2 = self.tiny_; 335 | csig2 = self.tiny_; 336 | } 337 | let salp2 = self._salp0; 338 | let calp2 = self._calp0 * csig2; 339 | if outmask & caps::DISTANCE != 0 { 340 | s12 = if arcmode { 341 | self._b * ((1.0 + self._A1m1) * sig12 + AB1) 342 | } else { 343 | s12_a12 344 | } 345 | } 346 | if outmask & caps::LONGITUDE != 0 { 347 | let somg2 = self._salp0 * ssig2; 348 | let comg2 = csig2; 349 | let E = 1.0_f64.copysign(self._salp0); 350 | let omg12 = if outmask & caps::LONG_UNROLL != 0 { 351 | E * (sig12 - (ssig2.atan2(csig2) - self._ssig1.atan2(self._csig1)) 352 | + ((E * somg2).atan2(comg2) - (E * self._somg1).atan2(self._comg1))) 353 | } else { 354 | (somg2 * self._comg1 - comg2 * self._somg1) 355 | .atan2(comg2 * self._comg1 + somg2 * self._somg1) 356 | }; 357 | let lam12 = omg12 358 | + self._A3c 359 | * (sig12 360 | + (geomath::sin_cos_series(true, ssig2, csig2, &self._C3a) - self._B31)); 361 | let lon12 = lam12.to_degrees(); 362 | lon2 = if outmask & caps::LONG_UNROLL != 0 { 363 | self.lon1 + lon12 364 | } else { 365 | geomath::ang_normalize( 366 | geomath::ang_normalize(self.lon1) + geomath::ang_normalize(lon12), 367 | ) 368 | }; 369 | }; 370 | 371 | if outmask & caps::LATITUDE != 0 { 372 | lat2 = geomath::atan2d(sbet2, self._f1 * cbet2); 373 | } 374 | if outmask & caps::AZIMUTH != 0 { 375 | azi2 = geomath::atan2d(salp2, calp2); 376 | } 377 | if outmask & (caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 { 378 | let B22 = geomath::sin_cos_series(true, ssig2, csig2, &self._C2a); 379 | let AB2 = (1.0 + self._A2m1) * (B22 - self._B21); 380 | let J12 = (self._A1m1 - self._A2m1) * sig12 + (AB1 - AB2); 381 | if outmask & caps::REDUCEDLENGTH != 0 { 382 | m12 = self._b 383 | * ((dn2 * (self._csig1 * ssig2) - self._dn1 * (self._ssig1 * csig2)) 384 | - self._csig1 * csig2 * J12); 385 | } 386 | if outmask & caps::GEODESICSCALE != 0 { 387 | let t = 388 | self._k2 * (ssig2 - self._ssig1) * (ssig2 + self._ssig1) / (self._dn1 + dn2); 389 | M12 = csig12 + (t * ssig2 - csig2 * J12) * self._ssig1 / self._dn1; 390 | M21 = csig12 - (t * self._ssig1 - self._csig1 * J12) * ssig2 / dn2; 391 | } 392 | } 393 | if outmask & caps::AREA != 0 { 394 | let B42 = geomath::sin_cos_series(false, ssig2, csig2, &self._C4a); 395 | let salp12: f64; 396 | let calp12: f64; 397 | if self._calp0 == 0.0 || self._salp0 == 0.0 { 398 | salp12 = salp2 * self.calp1 - calp2 * self.salp1; 399 | calp12 = calp2 * self.calp1 + salp2 * self.salp1; 400 | } else { 401 | salp12 = self._calp0 402 | * self._salp0 403 | * (if csig12 <= 0.0 { 404 | self._csig1 * (1.0 - csig12) + ssig12 * self._ssig1 405 | } else { 406 | ssig12 * (self._csig1 * ssig12 / (1.0 + csig12) + self._ssig1) 407 | }); 408 | calp12 = geomath::sq(self._salp0) + geomath::sq(self._calp0) * self._csig1 * csig2; 409 | } 410 | S12 = self._c2 * salp12.atan2(calp12) + self._A4 * (B42 - self._B41); 411 | } 412 | a12 = if arcmode { s12_a12 } else { sig12.to_degrees() }; 413 | (a12, lat2, lon2, azi2, s12, m12, M12, M21, S12) 414 | } 415 | 416 | // not currently used, but maybe some day 417 | #[allow(dead_code)] 418 | pub fn Position(&self, s12: f64, outmask: Option) -> HashMap { 419 | let outmask = match outmask { 420 | Some(outmask) => outmask, 421 | None => caps::STANDARD, 422 | }; 423 | let mut result: HashMap = HashMap::new(); 424 | result.insert("lat1".to_string(), self.lat1); 425 | result.insert("azi1".to_string(), self.azi1); 426 | result.insert("s12".to_string(), s12); 427 | let lon1 = if outmask & caps::LONG_UNROLL != 0 { 428 | self.lon1 429 | } else { 430 | geomath::ang_normalize(self.lon1) 431 | }; 432 | result.insert("lon1".to_string(), lon1); 433 | 434 | let (a12, lat2, lon2, azi2, _s12, m12, M12, M21, S12) = 435 | self._gen_position(false, s12, outmask); 436 | let outmask = outmask & caps::OUT_MASK; 437 | result.insert("a12".to_string(), a12); 438 | if outmask & caps::LATITUDE != 0 { 439 | result.insert("lat2".to_string(), lat2); 440 | } 441 | if outmask & caps::LONGITUDE != 0 { 442 | result.insert("lon2".to_string(), lon2); 443 | } 444 | if outmask & caps::AZIMUTH != 0 { 445 | result.insert("azi2".to_string(), azi2); 446 | } 447 | if outmask & caps::REDUCEDLENGTH != 0 { 448 | result.insert("m12".to_string(), m12); 449 | } 450 | if outmask & caps::GEODESICSCALE != 0 { 451 | result.insert("M12".to_string(), M12); 452 | result.insert("M21".to_string(), M21); 453 | } 454 | if outmask & caps::AREA != 0 { 455 | result.insert("S12".to_string(), S12); 456 | } 457 | result 458 | } 459 | } 460 | 461 | #[cfg(test)] 462 | mod tests { 463 | use super::*; 464 | use geodesic::Geodesic; 465 | 466 | #[test] 467 | fn test_gen_position() { 468 | let geod = Geodesic::wgs84(); 469 | let gl = GeodesicLine::new(&geod, 0.0, 0.0, 10.0, None, None, None); 470 | let res = gl._gen_position(false, 150.0, 3979); 471 | assert_eq!(res.0, 0.0013520059461334633); 472 | assert_eq!(res.1, 0.0013359451088740494); 473 | assert_eq!(res.2, 0.00023398621812867812); 474 | assert_eq!(res.3, 10.000000002727887); 475 | assert_eq!(res.4, 150.0); 476 | assert!(res.5.is_nan()); 477 | assert!(res.6.is_nan()); 478 | assert!(res.7.is_nan()); 479 | assert!(res.8.is_nan()); 480 | } 481 | 482 | #[test] 483 | fn test_init() { 484 | let geod = Geodesic::wgs84(); 485 | let gl = GeodesicLine::new(&geod, 0.0, 0.0, 0.0, None, None, None); 486 | assert_eq!(gl._a, 6378137.0); 487 | assert_eq!(gl.f, 0.0033528106647474805); 488 | assert_eq!(gl._b, 6356752.314245179); 489 | assert_eq!(gl._c2, 40589732499314.76); 490 | assert_eq!(gl._f1, 0.9966471893352525); 491 | assert_eq!(gl.caps, 36747); 492 | assert_eq!(gl.lat1, 0.0); 493 | assert_eq!(gl.lon1, 0.0); 494 | assert_eq!(gl.azi1, 0.0); 495 | assert_eq!(gl.salp1, 0.0); 496 | assert_eq!(gl.calp1, 1.0); 497 | assert_eq!(gl._dn1, 1.0); 498 | assert_eq!(gl._salp0, 0.0); 499 | assert_eq!(gl._calp0, 1.0); 500 | assert_eq!(gl._ssig1, 0.0); 501 | assert_eq!(gl._somg1, 0.0); 502 | assert_eq!(gl._csig1, 1.0); 503 | assert_eq!(gl._comg1, 1.0); 504 | assert_eq!(gl._k2, geod._ep2); 505 | assert!(gl._s13.is_nan()); 506 | assert!(gl._a13.is_nan()); 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /src/geomath.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | #![allow(clippy::excessive_precision)] 3 | 4 | pub const DIGITS: u64 = 53; 5 | pub const TWO: f64 = 2.0; 6 | 7 | pub fn get_epsilon() -> f64 { 8 | TWO.powi(1 - DIGITS as i32) 9 | } 10 | 11 | pub fn get_min_val() -> f64 { 12 | TWO.powi(-1022) 13 | } 14 | 15 | // Square 16 | pub fn sq(x: f64) -> f64 { 17 | x.powi(2) 18 | } 19 | 20 | // We use the built-in impl (f64::cbrt) rather than this. 21 | // Real cube root 22 | pub fn cbrt(x: f64) -> f64 { 23 | // y = math.pow(abs(x), 1/3.0) 24 | let y = x.abs().powf(1.0 / 3.0); 25 | 26 | // return y if x > 0 else (-y if x < 0 else x) 27 | if x > 0.0 { 28 | y 29 | } else if x < 0.0 { 30 | -y 31 | } else { 32 | x 33 | } 34 | } 35 | 36 | // Normalize a two-vector 37 | pub fn norm(x: &mut f64, y: &mut f64) { 38 | let r = x.hypot(*y); 39 | *x /= r; 40 | *y /= r; 41 | } 42 | 43 | // Error free transformation of a sum 44 | pub fn sum(u: f64, v: f64) -> (f64, f64) { 45 | let s = u + v; 46 | let up = s - v; 47 | let vpp = s - up; 48 | let up = up - u; 49 | let vpp = vpp - v; 50 | let t = -(up + vpp); 51 | (s, t) 52 | } 53 | 54 | // Evaluate a polynomial 55 | pub fn polyval(n: usize, p: &[f64], x: f64) -> f64 { 56 | let mut y = p[0]; 57 | for val in &p[1..=n] { 58 | y = y * x + val; 59 | } 60 | y 61 | } 62 | 63 | // Round an angle so taht small values underflow to 0 64 | pub fn ang_round(x: f64) -> f64 { 65 | // The makes the smallest gap in x = 1/16 - nextafter(1/16, 0) = 1/2^57 66 | // for reals = 0.7 pm on the earth if x is an angle in degrees. (This 67 | // is about 1000 times more resolution than we get with angles around 90 68 | // degrees.) We use this to avoid having to deal with near singular 69 | // cases when x is non-zero but tiny (e.g., 1.0e-200). 70 | let z = 1.0 / 16.0; 71 | let mut y = x.abs(); 72 | // The compiler mustn't "simplify" z - (z - y) to y 73 | if y < z { 74 | y = z - (z - y); 75 | }; 76 | if x == 0.0 { 77 | 0.0 78 | } else if x < 0.0 { 79 | -y 80 | } else { 81 | y 82 | } 83 | } 84 | 85 | /// remainder of x/y in the range [-y/2, y/2] 86 | fn remainder(x: f64, y: f64) -> f64 { 87 | // z = math.fmod(x, y) if Math.isfinite(x) else Math.nan 88 | let z = if x.is_finite() { x % y } else { f64::NAN }; 89 | 90 | // # On Windows 32-bit with python 2.7, math.fmod(-0.0, 360) = +0.0 91 | // # This fixes this bug. See also Math::AngNormalize in the C++ library. 92 | // # sincosd has a similar fix. 93 | // z = x if x == 0 else z 94 | let z = if x == 0.0 { x } else { z }; 95 | 96 | // return (z + y if z < -y/2 else 97 | // (z if z < y/2 else z -y)) 98 | if z < -y / 2.0 { 99 | z + y 100 | } else if z < y / 2.0 { 101 | z 102 | } else { 103 | z - y 104 | } 105 | } 106 | 107 | /// reduce angle to (-180,180] 108 | pub fn ang_normalize(x: f64) -> f64 { 109 | // y = Math.remainder(x, 360) 110 | // return 180 if y == -180 else y 111 | let y = remainder(x, 360.0); 112 | if y == -180.0 { 113 | 180.0 114 | } else { 115 | y 116 | } 117 | } 118 | 119 | // Replace angles outside [-90,90] with NaN 120 | pub fn lat_fix(x: f64) -> f64 { 121 | if x.abs() > 90.0 { 122 | f64::NAN 123 | } else { 124 | x 125 | } 126 | } 127 | 128 | // compute y - x and reduce to [-180,180] accurately 129 | pub fn ang_diff(x: f64, y: f64) -> (f64, f64) { 130 | let (d, t) = sum(ang_normalize(-x), ang_normalize(y)); 131 | let d = ang_normalize(d); 132 | if d == 180.0 && t > 0.0 { 133 | sum(-180.0, t) 134 | } else { 135 | sum(d, t) 136 | } 137 | } 138 | 139 | /// Compute sine and cosine of x in degrees 140 | pub fn sincosd(x: f64) -> (f64, f64) { 141 | let (mut r, q) = libm::remquo(x, 90.0); 142 | 143 | r = r.to_radians(); 144 | 145 | let (mut sinx, mut cosx) = r.sin_cos(); 146 | 147 | (sinx, cosx) = match q as u32 & 3 { 148 | 0 => (sinx, cosx), 149 | 1 => (cosx, -sinx), 150 | 2 => (-sinx, -cosx), 151 | 3 => (-cosx, sinx), 152 | _ => unreachable!(), 153 | }; 154 | 155 | // special values from F.10.1.12 156 | cosx += 0.0; 157 | 158 | // special values from F.10.1.13 159 | if sinx == 0.0 { 160 | sinx = sinx.copysign(x); 161 | } 162 | (sinx, cosx) 163 | } 164 | 165 | // Compute atan2(y, x) with result in degrees 166 | pub fn atan2d(y: f64, x: f64) -> f64 { 167 | let mut x = x; 168 | let mut y = y; 169 | let mut q = if y.abs() > x.abs() { 170 | std::mem::swap(&mut x, &mut y); 171 | 2.0 172 | } else { 173 | 0.0 174 | }; 175 | if x < 0.0 { 176 | q += 1.0; 177 | x = -x; 178 | } 179 | let mut ang = y.atan2(x).to_degrees(); 180 | if q == 1.0 { 181 | ang = if y >= 0.0 { 180.0 - ang } else { -180.0 - ang }; 182 | } else if q == 2.0 { 183 | ang = 90.0 - ang; 184 | } else if q == 3.0 { 185 | ang += -90.0; 186 | } 187 | ang 188 | } 189 | 190 | pub fn eatanhe(x: f64, es: f64) -> f64 { 191 | if es > 0.0 { 192 | es * (es * x).atanh() 193 | } else { 194 | -es * (es * x).atan() 195 | } 196 | } 197 | 198 | // Functions that used to be inside Geodesic 199 | pub fn sin_cos_series(sinp: bool, sinx: f64, cosx: f64, c: &[f64]) -> f64 { 200 | let mut k = c.len(); 201 | let mut n: i64 = k as i64 - if sinp { 1 } else { 0 }; 202 | let ar: f64 = 2.0 * (cosx - sinx) * (cosx + sinx); 203 | let mut y1 = 0.0; 204 | let mut y0: f64 = if n & 1 != 0 { 205 | k -= 1; 206 | c[k] 207 | } else { 208 | 0.0 209 | }; 210 | n /= 2; 211 | while n > 0 { 212 | n -= 1; 213 | k -= 1; 214 | y1 = ar * y0 - y1 + c[k]; 215 | k -= 1; 216 | y0 = ar * y1 - y0 + c[k]; 217 | } 218 | if sinp { 219 | 2.0 * sinx * cosx * y0 220 | } else { 221 | cosx * (y0 - y1) 222 | } 223 | } 224 | 225 | // Solve astroid equation 226 | pub fn astroid(x: f64, y: f64) -> f64 { 227 | let p = sq(x); 228 | let q = sq(y); 229 | let r = (p + q - 1.0) / 6.0; 230 | if !(q == 0.0 && r <= 0.0) { 231 | let s = p * q / 4.0; 232 | let r2 = sq(r); 233 | let r3 = r * r2; 234 | let disc = s * (s + 2.0 * r3); 235 | let mut u = r; 236 | if disc >= 0.0 { 237 | let mut t3 = s + r3; 238 | t3 += if t3 < 0.0 { -disc.sqrt() } else { disc.sqrt() }; 239 | let t = cbrt(t3); // we could use built-in T.cbrt 240 | u += t + if t != 0.0 { r2 / t } else { 0.0 }; 241 | } else { 242 | let ang = (-disc).sqrt().atan2(-(s + r3)); 243 | u += 2.0 * r * (ang / 3.0).cos(); 244 | } 245 | let v = (sq(u) + q).sqrt(); 246 | let uv = if u < 0.0 { q / (v - u) } else { u + v }; 247 | let w = (uv - q) / (2.0 * v); 248 | uv / ((uv + sq(w)).sqrt() + w) 249 | } else { 250 | 0.0 251 | } 252 | } 253 | 254 | pub fn _A1m1f(eps: f64, geodesic_order: usize) -> f64 { 255 | const COEFF: [f64; 5] = [1.0, 4.0, 64.0, 0.0, 256.0]; 256 | let m = geodesic_order / 2; 257 | let t = polyval(m, &COEFF, sq(eps)) / COEFF[m + 1]; 258 | (t + eps) / (1.0 - eps) 259 | } 260 | 261 | pub fn _C1f(eps: f64, c: &mut [f64], geodesic_order: usize) { 262 | const COEFF: [f64; 18] = [ 263 | -1.0, 6.0, -16.0, 32.0, -9.0, 64.0, -128.0, 2048.0, 9.0, -16.0, 768.0, 3.0, -5.0, 512.0, 264 | -7.0, 1280.0, -7.0, 2048.0, 265 | ]; 266 | let eps2 = sq(eps); 267 | let mut d = eps; 268 | let mut o = 0; 269 | // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)` 270 | // but benching (rust-1.75) shows that it would be slower. 271 | #[allow(clippy::needless_range_loop)] 272 | for l in 1..=geodesic_order { 273 | let m = (geodesic_order - l) / 2; 274 | c[l] = d * polyval(m, &COEFF[o..], eps2) / COEFF[o + m + 1]; 275 | o += m + 2; 276 | d *= eps; 277 | } 278 | } 279 | 280 | pub fn _C1pf(eps: f64, c: &mut [f64], geodesic_order: usize) { 281 | const COEFF: [f64; 18] = [ 282 | 205.0, -432.0, 768.0, 1536.0, 4005.0, -4736.0, 3840.0, 12288.0, -225.0, 116.0, 384.0, 283 | -7173.0, 2695.0, 7680.0, 3467.0, 7680.0, 38081.0, 61440.0, 284 | ]; 285 | let eps2 = sq(eps); 286 | let mut d = eps; 287 | let mut o = 0; 288 | // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)` 289 | // but benching (rust-1.75) shows that it would be slower. 290 | #[allow(clippy::needless_range_loop)] 291 | for l in 1..=geodesic_order { 292 | let m = (geodesic_order - l) / 2; 293 | c[l] = d * polyval(m, &COEFF[o..], eps2) / COEFF[o + m + 1]; 294 | o += m + 2; 295 | d *= eps; 296 | } 297 | } 298 | 299 | pub fn _A2m1f(eps: f64, geodesic_order: usize) -> f64 { 300 | const COEFF: [f64; 5] = [-11.0, -28.0, -192.0, 0.0, 256.0]; 301 | let m = geodesic_order / 2; 302 | let t = polyval(m, &COEFF, sq(eps)) / COEFF[m + 1]; 303 | (t - eps) / (1.0 + eps) 304 | } 305 | 306 | pub fn _C2f(eps: f64, c: &mut [f64], geodesic_order: usize) { 307 | const COEFF: [f64; 18] = [ 308 | 1.0, 2.0, 16.0, 32.0, 35.0, 64.0, 384.0, 2048.0, 15.0, 80.0, 768.0, 7.0, 35.0, 512.0, 63.0, 309 | 1280.0, 77.0, 2048.0, 310 | ]; 311 | let eps2 = sq(eps); 312 | let mut d = eps; 313 | let mut o = 0; 314 | // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)` 315 | // but benching (rust-1.75) shows that it would be slower. 316 | #[allow(clippy::needless_range_loop)] 317 | for l in 1..=geodesic_order { 318 | let m = (geodesic_order - l) / 2; 319 | c[l] = d * polyval(m, &COEFF[o..], eps2) / COEFF[o + m + 1]; 320 | o += m + 2; 321 | d *= eps; 322 | } 323 | } 324 | 325 | #[cfg(test)] 326 | mod tests { 327 | use super::*; 328 | use approx::assert_relative_eq; 329 | // Results for the assertions are taken by running the python implementation 330 | 331 | #[test] 332 | fn test_sincosd() { 333 | let res = sincosd(-77.03196); 334 | assert_relative_eq!(res.0, -0.9744953925159129); 335 | assert_relative_eq!(res.1, 0.22440750870961693); 336 | 337 | let res = sincosd(69.48894); 338 | assert_relative_eq!(res.0, 0.9366045700708676); 339 | assert_relative_eq!(res.1, 0.3503881837653281); 340 | let res = sincosd(-1.0); 341 | assert_relative_eq!(res.0, -0.01745240643728351); 342 | assert_relative_eq!(res.1, 0.9998476951563913); 343 | } 344 | 345 | #[test] 346 | fn test__C2f() { 347 | let mut c = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]; 348 | _C2f(0.12, &mut c, 6); 349 | assert_eq!( 350 | c, 351 | vec![ 352 | 1.0, 353 | 0.0601087776, 354 | 0.00270653103, 355 | 0.000180486, 356 | 1.4215824e-05, 357 | 1.22472e-06, 358 | 1.12266e-07 359 | ] 360 | ) 361 | } 362 | 363 | #[test] 364 | fn test__A2m1f() { 365 | assert_eq!(_A2m1f(0.12, 6), -0.11680607884285714); 366 | } 367 | 368 | #[test] 369 | fn test__C1pf() { 370 | let mut c = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]; 371 | _C1pf(0.12, &mut c, 6); 372 | assert_eq!( 373 | c, 374 | vec![ 375 | 1.0, 376 | 0.059517321000000005, 377 | 0.004421053215, 378 | 0.0005074200000000001, 379 | 6.997613759999999e-05, 380 | 1.1233080000000001e-05, 381 | 1.8507366e-06 382 | ] 383 | ) 384 | } 385 | 386 | #[test] 387 | fn test__C1f() { 388 | let mut c = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]; 389 | _C1f(0.12, &mut c, 6); 390 | assert_eq!( 391 | c, 392 | vec![ 393 | 1.0, 394 | -0.059676777599999994, 395 | -0.000893533122, 396 | -3.57084e-05, 397 | -2.007504e-06, 398 | -1.3607999999999999e-07, 399 | -1.0205999999999999e-08 400 | ] 401 | ) 402 | } 403 | 404 | #[test] 405 | fn test__A1m1f() { 406 | assert_eq!(_A1m1f(0.12, 6), 0.1404582405272727); 407 | } 408 | 409 | #[test] 410 | fn test_astroid() { 411 | assert_eq!(astroid(21.0, 12.0), 23.44475767500982); 412 | } 413 | 414 | #[test] 415 | fn test_sin_cos_series() { 416 | assert_eq!( 417 | sin_cos_series( 418 | false, 419 | -0.8928657853278468, 420 | 0.45032287238256896, 421 | &[ 422 | 0.6660771734724675, 423 | 1.5757752625233906e-05, 424 | 3.8461688963148916e-09, 425 | 1.3040960748120204e-12, 426 | 5.252912023008548e-16, 427 | 2.367770858285795e-19 428 | ], 429 | ), 430 | 0.29993425660538664 431 | ); 432 | 433 | assert_eq!( 434 | sin_cos_series( 435 | false, 436 | -0.8928657853278468, 437 | 0.45032287238256896, 438 | &[0., 1., 2., 3., 4., 5.], 439 | ), 440 | 1.8998562852254026 441 | ); 442 | assert_eq!( 443 | sin_cos_series( 444 | true, 445 | 0.2969032234925426, 446 | 0.9549075745221299, 447 | &[ 448 | 0.0, 449 | -0.0003561309485314716, 450 | -3.170731714689771e-08, 451 | -7.527972480734327e-12, 452 | -2.5133854116682488e-15, 453 | -1.0025061462383107e-18, 454 | -4.462794158625518e-22 455 | ], 456 | ), 457 | -0.00020196665516199853 458 | ); 459 | assert_eq!( 460 | sin_cos_series( 461 | true, 462 | -0.8928657853278468, 463 | 0.45032287238256896, 464 | &[ 465 | 0.0, 466 | -0.0003561309485314716, 467 | -3.170731714689771e-08, 468 | -7.527972480734327e-12, 469 | -2.5133854116682488e-15, 470 | -1.0025061462383107e-18, 471 | -4.462794158625518e-22 472 | ], 473 | ), 474 | 0.00028635444718997857 475 | ); 476 | 477 | assert_eq!( 478 | sin_cos_series(true, 0.12, 0.21, &[1.0, 2.0]), 479 | 0.10079999999999999 480 | ); 481 | assert_eq!( 482 | sin_cos_series( 483 | true, 484 | -0.024679833885152578, 485 | 0.9996954065111039, 486 | &[ 487 | 0.0, 488 | -0.0008355098973052918, 489 | -1.7444619952659748e-07, 490 | -7.286557795511902e-11, 491 | -3.80472772706481e-14, 492 | -2.2251271876594078e-17, 493 | 1.2789961247944744e-20 494 | ], 495 | ), 496 | 4.124513511893872e-05 497 | ); 498 | } 499 | 500 | // corresponding to tests/signtest.cpp 501 | mod sign_test { 502 | use super::*; 503 | fn is_equiv(x: f64, y: f64) -> bool { 504 | (x.is_nan() && y.is_nan()) || (x == y && x.is_sign_positive() == y.is_sign_positive()) 505 | } 506 | 507 | macro_rules! check_sincosd { 508 | ($x: expr, $expected_sin: expr, $expected_cos: expr) => { 509 | let (sinx, cosx) = sincosd($x); 510 | assert!( 511 | is_equiv(sinx, $expected_sin), 512 | "sinx({}) = {}, but got {}", 513 | $x, 514 | $expected_sin, 515 | sinx 516 | ); 517 | assert!( 518 | is_equiv(cosx, $expected_cos), 519 | "cosx({}) = {}, but got {}", 520 | $x, 521 | $expected_cos, 522 | cosx 523 | ); 524 | }; 525 | } 526 | 527 | #[test] 528 | fn sin_cosd() { 529 | check_sincosd!(f64::NEG_INFINITY, f64::NAN, f64::NAN); 530 | check_sincosd!(-810.0, -1.0, 0.0); 531 | check_sincosd!(-720.0, -0.0, 1.0); 532 | check_sincosd!(-630.0, 1.0, 0.0); 533 | check_sincosd!(-540.0, -0.0, -1.0); 534 | check_sincosd!(-450.0, -1.0, 0.0); 535 | check_sincosd!(-360.0, -0.0, 1.0); 536 | check_sincosd!(-270.0, 1.0, 0.0); 537 | check_sincosd!(-180.0, -0.0, -1.0); 538 | check_sincosd!(-90.0, -1.0, 0.0); 539 | check_sincosd!(-0.0, -0.0, 1.0); 540 | check_sincosd!(0.0, 0.0, 1.0); 541 | check_sincosd!(90.0, 1.0, 0.0); 542 | check_sincosd!(180.0, 0.0, -1.0); 543 | check_sincosd!(270.0, -1.0, 0.0); 544 | check_sincosd!(360.0, 0.0, 1.0); 545 | check_sincosd!(450.0, 1.0, 0.0); 546 | check_sincosd!(540.0, 0.0, -1.0); 547 | check_sincosd!(630.0, -1.0, 0.0); 548 | check_sincosd!(720.0, 0.0, 1.0); 549 | check_sincosd!(810.0, 1.0, 0.0); 550 | check_sincosd!(f64::INFINITY, f64::NAN, f64::NAN); 551 | check_sincosd!(f64::NAN, f64::NAN, f64::NAN); 552 | } 553 | } 554 | } 555 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A subset of [geographiclib](https://geographiclib.sourceforge.io/) implemented in Rust. 2 | //! 3 | //! # Examples 4 | //! 5 | //! ```rust 6 | //! // Determine the point 10000 km NE of JFK - the "direct" geodesic calculation. 7 | //! use geographiclib_rs::{Geodesic, DirectGeodesic}; 8 | //! 9 | //! let g = Geodesic::wgs84(); 10 | //! let jfk_lat = 40.64; 11 | //! let jfk_lon = -73.78; 12 | //! let northeast_azimuth = 45.0; 13 | //! 14 | //! let (lat, lon, az) = g.direct(jfk_lat, jfk_lon, northeast_azimuth, 10e6); 15 | //! 16 | //! use approx::assert_relative_eq; 17 | //! assert_relative_eq!(lat, 32.621100463725796); 18 | //! assert_relative_eq!(lon, 49.052487092959836); 19 | //! assert_relative_eq!(az, 140.4059858768007); 20 | //! ``` 21 | //! 22 | //! ```rust 23 | //! // Determine the distance between two points - the "inverse" geodesic calculation. 24 | //! use geographiclib_rs::{Geodesic, InverseGeodesic}; 25 | //! 26 | //! let g = Geodesic::wgs84(); 27 | //! let p1 = (34.095925, -118.2884237); 28 | //! let p2 = (59.4323439, 24.7341649); 29 | //! let s12: f64 = g.inverse(p1.0, p1.1, p2.0, p2.1); 30 | //! 31 | //! use approx::assert_relative_eq; 32 | //! assert_relative_eq!(s12, 9094718.72751138); 33 | //! ``` 34 | //! 35 | //! ```rust 36 | //! // Determine the perimeter and area of a polygon. 37 | //! use geographiclib_rs::{Geodesic, PolygonArea, Winding}; 38 | //! 39 | //! let g = Geodesic::wgs84(); 40 | //! let mut pa = PolygonArea::new(&g, Winding::CounterClockwise); 41 | //! pa.add_point(0.0, 0.0); 42 | //! pa.add_point(0.0, 1.0); 43 | //! pa.add_point(1.0, 1.0); 44 | //! pa.add_point(1.0, 0.0); 45 | //! 46 | //! let (perimeter_m, area_m_squared, num_points) = pa.compute(false); 47 | //! 48 | //! use approx::assert_relative_eq; 49 | //! assert_relative_eq!(perimeter_m, 443770.91724830196); 50 | //! assert_relative_eq!(area_m_squared, 12308778361.469452); 51 | //! assert_eq!(num_points, 4); 52 | //! ``` 53 | //! 54 | //! ```rust 55 | //! // Determine the distance between rovers Pathfinder and Curiosity on Mars 56 | //! use geographiclib_rs::{Geodesic, InverseGeodesic}; 57 | //! 58 | //! let mars = Geodesic::new(3396190.0, 1.0 / 169.8944472); 59 | //! let pathfinder = (19.26, 326.75); 60 | //! let curiosity = (-4.765700445, 137.39820983); 61 | //! let distance_m: f64 = mars.inverse(curiosity.0, curiosity.1, pathfinder.0, pathfinder.1); 62 | //! 63 | //! assert_eq!(distance_m.round(), 9639113.0); 64 | //! ``` 65 | //! 66 | //! # Features 67 | //! 68 | //! 1. `accurate`: Enabled by default. Use the [`accurate`](https://docs.rs/accurate/latest/accurate/) crate to provide high accuracy polygon areas and perimeters in `PolygonArea`. Can be disabled for better performance or when `PolygonArea` is not being used. 69 | 70 | // Since this library is a port of an existing (cpp) codebase, there are times we opt 71 | // to follow the upstream implementation rather than follow rust idioms. 72 | #![allow(clippy::bool_to_int_with_if)] 73 | #![allow(clippy::manual_range_contains)] 74 | #![allow(clippy::excessive_precision)] 75 | 76 | mod geodesic; 77 | pub use geodesic::{DirectGeodesic, Geodesic, InverseGeodesic}; 78 | 79 | pub mod geodesic_capability; 80 | pub use geodesic_capability as capability; 81 | 82 | mod geodesic_line; 83 | pub use geodesic_line::GeodesicLine; 84 | mod geomath; 85 | mod polygon_area; 86 | pub use polygon_area::PolygonArea; 87 | pub use polygon_area::Winding; 88 | -------------------------------------------------------------------------------- /src/polygon_area.rs: -------------------------------------------------------------------------------- 1 | use crate::geomath::ang_diff; 2 | use crate::geomath::ang_normalize; 3 | use crate::Geodesic; 4 | 5 | use crate::geodesic_capability as caps; 6 | 7 | const POLYGONAREA_MASK: u64 = 8 | caps::LATITUDE | caps::LONGITUDE | caps::DISTANCE | caps::AREA | caps::LONG_UNROLL; 9 | 10 | #[cfg(feature = "accurate")] 11 | use accurate::traits::*; 12 | 13 | /// Clockwise or CounterClockwise winding 14 | /// 15 | /// The standard winding of a Simple Feature polygon is counter-clockwise. However, if the polygon is a hole, then the winding is clockwise. 16 | /// ESRI Shapefile polygons are opposite, with the outer-ring being clockwise and holes being counter-clockwise. 17 | #[derive(Debug, Default, Copy, Clone)] 18 | pub enum Winding { 19 | Clockwise, 20 | #[default] 21 | CounterClockwise, 22 | } 23 | 24 | /// Compute the perimeter and area of a polygon on a Geodesic. 25 | #[derive(Debug, Clone)] 26 | pub struct PolygonArea<'a> { 27 | geoid: &'a Geodesic, 28 | winding: Winding, 29 | num: usize, 30 | 31 | #[cfg(not(feature = "accurate"))] 32 | areasum: f64, 33 | 34 | #[cfg(feature = "accurate")] 35 | areasum: accurate::sum::Sum2, 36 | 37 | #[cfg(not(feature = "accurate"))] 38 | perimetersum: f64, 39 | 40 | #[cfg(feature = "accurate")] 41 | perimetersum: accurate::sum::Sum2, 42 | 43 | crossings: i64, 44 | initial_lat: f64, 45 | initial_lon: f64, 46 | latest_lat: f64, 47 | latest_lon: f64, 48 | } 49 | 50 | /// PolygonArea can be used to compute the perimeter and area of a polygon on a Geodesic. 51 | /// 52 | /// # Example 53 | /// ```rust 54 | /// use geographiclib_rs::{Geodesic, PolygonArea, Winding}; 55 | /// 56 | /// let g = Geodesic::wgs84(); 57 | /// let mut pa = PolygonArea::new(&g, Winding::CounterClockwise); 58 | /// 59 | /// pa.add_point(0.0, 0.0); 60 | /// pa.add_point(0.0, 1.0); 61 | /// pa.add_point(1.0, 1.0); 62 | /// pa.add_point(1.0, 0.0); 63 | /// 64 | /// let (perimeter, area, _num) = pa.compute(false); 65 | /// 66 | /// use approx::assert_relative_eq; 67 | /// assert_relative_eq!(perimeter, 443770.917248302); 68 | /// assert_relative_eq!(area, 12308778361.469452); 69 | /// ``` 70 | impl<'a> PolygonArea<'a> { 71 | /// Create a new PolygonArea using a Geodesic. 72 | pub fn new(geoid: &'a Geodesic, winding: Winding) -> Self { 73 | PolygonArea { 74 | geoid, 75 | winding, 76 | 77 | num: 0, 78 | 79 | #[cfg(not(feature = "accurate"))] 80 | areasum: 0.0, 81 | 82 | #[cfg(feature = "accurate")] 83 | areasum: accurate::sum::Sum2::zero(), 84 | 85 | #[cfg(not(feature = "accurate"))] 86 | perimetersum: 0.0, 87 | 88 | #[cfg(feature = "accurate")] 89 | perimetersum: accurate::sum::Sum2::zero(), 90 | 91 | crossings: 0, 92 | initial_lat: 0.0, 93 | initial_lon: 0.0, 94 | latest_lat: 0.0, 95 | latest_lon: 0.0, 96 | } 97 | } 98 | 99 | /// Add a point to the polygon 100 | pub fn add_point(&mut self, lat: f64, lon: f64) { 101 | if self.num == 0 { 102 | self.initial_lat = lat; 103 | self.initial_lon = lon; 104 | } else { 105 | #[allow(non_snake_case)] 106 | let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, S12) = self 107 | .geoid 108 | ._gen_inverse(self.latest_lat, self.latest_lon, lat, lon, POLYGONAREA_MASK); 109 | self.perimetersum += s12; 110 | self.areasum += S12; 111 | self.crossings += PolygonArea::transit(self.latest_lon, lon); 112 | } 113 | self.latest_lat = lat; 114 | self.latest_lon = lon; 115 | self.num += 1; 116 | } 117 | 118 | /// Add an edge to the polygon using an azimuth (in degrees) and a distance (in meters). This can only be called after at least one point has been added. 119 | /// 120 | /// # Panics 121 | /// Panics if no points have been added yet. 122 | pub fn add_edge(&mut self, azimuth: f64, distance: f64) { 123 | if self.num == 0 { 124 | panic!("PolygonArea::add_edge: No points added yet"); 125 | } 126 | 127 | #[allow(non_snake_case)] 128 | let (_a12, lat, lon, _azi2, _s12, _m12, _M12, _M21, S12) = self.geoid._gen_direct( 129 | self.latest_lat, 130 | self.latest_lon, 131 | azimuth, 132 | false, 133 | distance, 134 | POLYGONAREA_MASK, 135 | ); 136 | self.perimetersum += distance; 137 | self.areasum += S12; 138 | self.crossings += PolygonArea::transitdirect(self.latest_lon, lon); 139 | self.latest_lat = lat; 140 | self.latest_lon = lon; 141 | self.num += 1; 142 | } 143 | 144 | /// Consumes the PolygonArea and returns the following tuple: 145 | /// - 0: Perimeter in (meters) of the polygon. 146 | /// - 1: Area (meters²) of the polygon. 147 | /// - 2: Number of points added to the polygon. 148 | /// 149 | /// # Parameters 150 | /// 151 | /// - `sign`: Whether to allow negative values for the area. 152 | /// - `true`: Interpret an inversely wound polygon to be a "negative" area. This will produce incorrect results if the polygon covers over half the geodesic. See "Interpreting negative area values" below. 153 | /// - `false`: Always return a positive area. Inversely wound polygons are treated as if they are always wound the same way as the winding specified during creation of the PolygonArea. This is useful if you are dealing with very large polygons that might cover over half the geodesic, or if you are certain of your winding. 154 | /// 155 | /// # Interpreting negative area values 156 | /// 157 | /// A negative value can mean one of two things: 158 | /// 1. The winding of the polygon is opposite the winding specified during creation of the PolygonArea. 159 | /// 2. The polygon is larger than half the planet. In this case, to get the final area of the polygon, add the area of the planet to the result. If you expect to be dealing with polygons of this size pass `signed = false` to `compute()` to get the correct result. 160 | /// 161 | /// # Large polygon example 162 | /// ```rust 163 | /// use geographiclib_rs::{Geodesic, PolygonArea, Winding}; 164 | /// let g = Geodesic::wgs84(); 165 | /// 166 | /// // Describe a polygon that covers all of the earth EXCEPT this small square. 167 | /// // The outside of the polygon is in this square, the inside of the polygon is the rest of the earth. 168 | /// let mut pa = PolygonArea::new(&g, Winding::CounterClockwise); 169 | /// pa.add_point(0.0, 0.0); 170 | /// pa.add_point(1.0, 0.0); 171 | /// pa.add_point(1.0, 1.0); 172 | /// pa.add_point(0.0, 1.0); 173 | /// 174 | /// let (_perimeter, area, _count) = pa.compute(false); 175 | /// 176 | /// // Over 5 trillion square meters! 177 | /// assert_eq!(area, 510053312945726.94); 178 | /// ``` 179 | pub fn compute(mut self, sign: bool) -> (f64, f64, usize) { 180 | #[allow(non_snake_case)] 181 | let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, S12) = 182 | self.geoid._gen_inverse( 183 | self.latest_lat, 184 | self.latest_lon, 185 | self.initial_lat, 186 | self.initial_lon, 187 | POLYGONAREA_MASK, 188 | ); 189 | self.perimetersum += s12; 190 | self.areasum += S12; 191 | 192 | let perimetersum; 193 | let areasum; 194 | 195 | #[cfg(not(feature = "accurate"))] 196 | { 197 | perimetersum = self.perimetersum; 198 | areasum = self.areasum; 199 | } 200 | #[cfg(feature = "accurate")] 201 | { 202 | perimetersum = self.perimetersum.sum(); 203 | areasum = self.areasum.sum(); 204 | } 205 | 206 | self.crossings += PolygonArea::transit(self.latest_lon, self.initial_lon); 207 | 208 | // Properly take into account crossings when calculating area. 209 | let areasum = self.reduce_area(areasum, sign); 210 | 211 | (perimetersum, areasum, self.num) 212 | } 213 | 214 | /// Check what the perimeter and area would be if this point was added to the polygon without actually adding it 215 | pub fn test_point(&self, lat: f64, lon: f64, sign: bool) -> (f64, f64, usize) { 216 | let mut pa = self.clone(); 217 | pa.add_point(lat, lon); 218 | pa.compute(sign) 219 | } 220 | 221 | /// Check what the perimeter and area would be if this edge was added to the polygon without actually adding it 222 | pub fn test_edge(&self, azimuth: f64, distance: f64, sign: bool) -> (f64, f64, usize) { 223 | let mut pa = self.clone(); 224 | pa.add_edge(azimuth, distance); 225 | pa.compute(sign) 226 | } 227 | 228 | // Return 1 or -1 if crossing prime meridian in east or west direction. 229 | // Otherwise return zero. longitude = +/-0 considered to be positive. 230 | fn transit(lon1: f64, lon2: f64) -> i64 { 231 | let (lon12, _lon12s) = ang_diff(lon1, lon2); 232 | let lon1 = ang_normalize(lon1); 233 | let lon2 = ang_normalize(lon2); 234 | 235 | // Translation from the following cpp code: 236 | // https://github.com/geographiclib/geographiclib/blob/8bc13eb53acdd8bc4fbe4de212d42dbb29779476/src/PolygonArea.cpp#L22 237 | // 238 | // lon12 > 0 && ((lon1 < 0 && lon2 >= 0) || 239 | // (lon1 > 0 && lon2 == 0)) ? 1 : 240 | // (lon12 < 0 && lon1 >= 0 && lon2 < 0 ? -1 : 0); 241 | 242 | if lon12 > 0.0 && ((lon1 < 0.0 && lon2 >= 0.0) || (lon1 > 0.0 && lon2 == 0.0)) { 243 | 1 244 | } else if lon12 < 0.0 && lon1 >= 0.0 && lon2 < 0.0 { 245 | -1 246 | } else { 247 | 0 248 | } 249 | } 250 | 251 | fn transitdirect(lon1: f64, lon2: f64) -> i64 { 252 | // We want to compute exactly: floor(lon2 / 360) - floor(lon1 / 360) 253 | let lon1 = lon1 % 720.0; 254 | let lon2 = lon2 % 720.0; 255 | 256 | let a = if 0.0 <= lon2 && lon2 < 360.0 { 0 } else { 1 }; 257 | 258 | let b = if 0.0 <= lon1 && lon1 < 360.0 { 0 } else { 1 }; 259 | 260 | a - b 261 | } 262 | 263 | fn reduce_area(&self, area: f64, signed: bool) -> f64 { 264 | let geoid_area = self.geoid.area(); // Area of the planet 265 | let mut area = area % geoid_area; 266 | 267 | // Translation of the following cpp code: 268 | // if (crossings & 1) area += (area < 0 ? 1 : -1) * _area0/2; 269 | if self.crossings % 2 != 0 { 270 | if area < 0.0 { 271 | area += geoid_area / 2.0; 272 | } else { 273 | area -= geoid_area / 2.0; 274 | } 275 | } 276 | 277 | // Area is with the clockwise sense. If needed convert to counter-clockwise convention. 278 | area = match self.winding { 279 | Winding::Clockwise => area, 280 | Winding::CounterClockwise => -area, 281 | }; 282 | 283 | if signed { 284 | // Put area in (-geoid_area/2, geoid_area/2] 285 | if area > geoid_area / 2.0 { 286 | area -= geoid_area; 287 | } else if area <= -geoid_area / 2.0 { 288 | area += geoid_area; 289 | } 290 | } else { 291 | // Negative values mean the polygon is larger than half the planet. Correct for this. 292 | if area < 0.0 { 293 | area += geoid_area; 294 | } 295 | } 296 | 297 | area 298 | } 299 | } 300 | 301 | #[cfg(test)] 302 | mod tests { 303 | use super::*; 304 | use crate::Geodesic; 305 | use approx::assert_relative_eq; 306 | 307 | #[test] 308 | fn test_simple_polygonarea() { 309 | let geoid = Geodesic::wgs84(); 310 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 311 | 312 | pa.add_point(0.0, 0.0); 313 | pa.add_point(0.0, 1.0); 314 | pa.add_point(1.0, 1.0); 315 | pa.add_point(1.0, 0.0); 316 | 317 | let (perimeter, area, _count) = pa.compute(true); 318 | 319 | assert_relative_eq!(perimeter, 443770.917, epsilon = 1.0e-3); 320 | assert_relative_eq!(area, 12308778361.469, epsilon = 1.0e-3); 321 | 322 | let mut pa = PolygonArea::new(&geoid, Winding::Clockwise); 323 | 324 | pa.add_point(0.0, 0.0); 325 | pa.add_point(0.0, 1.0); 326 | pa.add_point(1.0, 1.0); 327 | pa.add_point(1.0, 0.0); 328 | 329 | let (perimeter, area, _count) = pa.compute(true); 330 | 331 | assert_relative_eq!(perimeter, 443770.917, epsilon = 1.0e-3); 332 | assert_relative_eq!(area, -12308778361.469, epsilon = 1.0e-3); 333 | } 334 | 335 | #[test] 336 | fn test_planimeter0() { 337 | // Copied from https://github.com/geographiclib/geographiclib-octave/blob/0662e05a432a040a60ab27c779fa09b554177ba9/inst/geographiclib_test.m#L644 338 | 339 | let geoid = Geodesic::wgs84(); 340 | 341 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 342 | pa.add_point(89.0, 0.0); 343 | pa.add_point(89.0, 90.0); 344 | pa.add_point(89.0, 180.0); 345 | pa.add_point(89.0, 270.0); 346 | let (perimeter, area, _count) = pa.compute(true); 347 | assert_relative_eq!(perimeter, 631819.8745, epsilon = 1.0e-4); 348 | assert_relative_eq!(area, 24952305678.0, epsilon = 1.0); 349 | 350 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 351 | pa.add_point(-89.0, 0.0); 352 | pa.add_point(-89.0, 90.0); 353 | pa.add_point(-89.0, 180.0); 354 | pa.add_point(-89.0, 270.0); 355 | let (perimeter, area, _count) = pa.compute(true); 356 | assert_relative_eq!(perimeter, 631819.8745, epsilon = 1.0e-4); 357 | assert_relative_eq!(area, -24952305678.0, epsilon = 1.0); 358 | 359 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 360 | pa.add_point(0.0, -1.0); 361 | pa.add_point(-1.0, 0.0); 362 | pa.add_point(0.0, 1.0); 363 | pa.add_point(1.0, 0.0); 364 | let (perimeter, area, _count) = pa.compute(true); 365 | assert_relative_eq!(perimeter, 627598.2731, epsilon = 1.0e-4); 366 | assert_relative_eq!(area, 24619419146.0, epsilon = 1.0); 367 | 368 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 369 | pa.add_point(90.0, 0.0); 370 | pa.add_point(0.0, 0.0); 371 | pa.add_point(0.0, 90.0); 372 | let (perimeter, area, _count) = pa.compute(true); 373 | assert_relative_eq!(perimeter, 30022685.0, epsilon = 1.0); 374 | assert_relative_eq!(area, 63758202715511.0, epsilon = 1.0); 375 | } 376 | 377 | #[test] 378 | fn test_planimeter5() { 379 | // Copied from https://github.com/geographiclib/geographiclib-octave/blob/0662e05a432a040a60ab27c779fa09b554177ba9/inst/geographiclib_test.m#L670 380 | 381 | let geoid = Geodesic::wgs84(); 382 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 383 | pa.add_point(89.0, 0.1); 384 | pa.add_point(89.0, 90.1); 385 | pa.add_point(89.0, -179.9); 386 | let (perimeter, area, _) = pa.compute(true); 387 | assert_relative_eq!(perimeter, 539297.0, epsilon = 1.0); 388 | assert_relative_eq!(area, 12476152838.5, epsilon = 1.0); 389 | } 390 | 391 | #[test] 392 | fn test_planimeter6() { 393 | // Copied from https://github.com/geographiclib/geographiclib-octave/blob/0662e05a432a040a60ab27c779fa09b554177ba9/inst/geographiclib_test.m#L679 394 | 395 | let geoid = Geodesic::wgs84(); 396 | 397 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 398 | pa.add_point(9.0, -0.00000000000001); 399 | pa.add_point(9.0, 180.0); 400 | pa.add_point(9.0, 0.0); 401 | let (perimeter, area, _) = pa.compute(true); 402 | assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0); 403 | assert_relative_eq!(area, 0.0, epsilon = 1.0); 404 | 405 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 406 | pa.add_point(9.0, 0.00000000000001); 407 | pa.add_point(9.0, 0.0); 408 | pa.add_point(9.0, 180.0); 409 | let (perimeter, area, _) = pa.compute(true); 410 | assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0); 411 | assert_relative_eq!(area, 0.0, epsilon = 1.0); 412 | 413 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 414 | pa.add_point(9.0, 0.00000000000001); 415 | pa.add_point(9.0, 180.0); 416 | pa.add_point(9.0, 0.0); 417 | let (perimeter, area, _) = pa.compute(true); 418 | assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0); 419 | assert_relative_eq!(area, 0.0, epsilon = 1.0); 420 | 421 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 422 | pa.add_point(9.0, -0.00000000000001); 423 | pa.add_point(9.0, 0.0); 424 | pa.add_point(9.0, 180.0); 425 | let (perimeter, area, _) = pa.compute(true); 426 | assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0); 427 | assert_relative_eq!(area, 0.0, epsilon = 1.0); 428 | } 429 | 430 | #[test] 431 | fn test_planimeter12() { 432 | // Copied from https://github.com/geographiclib/geographiclib-octave/blob/0662e05a432a040a60ab27c779fa09b554177ba9/inst/geographiclib_test.m#L701 433 | let geoid = Geodesic::wgs84(); 434 | 435 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 436 | pa.add_point(66.562222222, 0.0); 437 | pa.add_point(66.562222222, 180.0); 438 | pa.add_point(66.562222222, 360.0); 439 | let (perimeter, area, _) = pa.compute(true); 440 | assert_relative_eq!(perimeter, 10465729.0, epsilon = 1.0); 441 | assert_relative_eq!(area, 0.0); 442 | } 443 | 444 | #[test] 445 | fn test_planimeter12r() { 446 | // Copied from https://github.com/geographiclib/geographiclib-octave/blob/0662e05a432a040a60ab27c779fa09b554177ba9/inst/geographiclib_test.m#L710 447 | let geoid = Geodesic::wgs84(); 448 | 449 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 450 | 451 | pa.add_point(66.562222222, -0.0); 452 | pa.add_point(66.562222222, -180.0); 453 | pa.add_point(66.562222222, -360.0); 454 | 455 | let (perimeter, area, _) = pa.compute(true); 456 | assert_relative_eq!(perimeter, 10465729.0, epsilon = 1.0); 457 | assert_relative_eq!(area, 0.0); 458 | } 459 | 460 | #[test] 461 | fn test_planimeter13() { 462 | // Copied from https://github.com/geographiclib/geographiclib-octave/blob/0662e05a432a040a60ab27c779fa09b554177ba9/inst/geographiclib_test.m#L719 463 | 464 | let geoid = Geodesic::wgs84(); 465 | 466 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 467 | pa.add_point(89.0, -360.0); 468 | pa.add_point(89.0, -240.0); 469 | pa.add_point(89.0, -120.0); 470 | pa.add_point(89.0, 0.0); 471 | pa.add_point(89.0, 120.0); 472 | pa.add_point(89.0, 240.0); 473 | let (perimeter, area, _) = pa.compute(true); 474 | assert_relative_eq!(perimeter, 1160741.0, epsilon = 1.0); 475 | assert_relative_eq!(area, 32415230256.0, epsilon = 1.0); 476 | } 477 | 478 | #[test] 479 | fn test_planimeter15() { 480 | // Copied from https://github.com/geographiclib/geographiclib-octave/blob/0662e05a432a040a60ab27c779fa09b554177ba9/inst/geographiclib_test.m#LL728C14-L728C26 481 | 482 | let geoid = Geodesic::wgs84(); 483 | 484 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 485 | pa.add_point(2.0, 1.0); 486 | pa.add_point(1.0, 2.0); 487 | pa.add_point(3.0, 3.0); 488 | let (_, area, _) = pa.compute(true); 489 | assert_relative_eq!(area, 18454562325.45119, epsilon = 1.0e-4); 490 | 491 | // Switching the winding 492 | let mut pa = PolygonArea::new(&geoid, Winding::Clockwise); 493 | pa.add_point(2.0, 1.0); 494 | pa.add_point(1.0, 2.0); 495 | pa.add_point(3.0, 3.0); 496 | let (_, area, _) = pa.compute(true); 497 | assert_relative_eq!(area, -18454562325.45119, epsilon = 1.0e-4); 498 | 499 | // Swaping lat and lon 500 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 501 | pa.add_point(1.0, 2.0); 502 | pa.add_point(2.0, 1.0); 503 | pa.add_point(3.0, 3.0); 504 | let (_, area, _) = pa.compute(true); 505 | assert_relative_eq!(area, -18454562325.45119, epsilon = 1.0e-4); 506 | } 507 | 508 | #[test] 509 | fn test_planimeter19() { 510 | // Test degenerate polygons 511 | 512 | // Copied from https://github.com/geographiclib/geographiclib-js/blob/57137fdcf4ba56718c64b909b00331754b6efceb/geodesic/test/geodesictest.js#L801 513 | // Testing degenrate polygons. 514 | let geoid = Geodesic::wgs84(); 515 | 516 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 517 | let (perimeter, area, _) = pa.clone().compute(true); 518 | assert_relative_eq!(perimeter, 0.0); 519 | assert_relative_eq!(area, 0.0); 520 | 521 | let (perimeter, area, _) = pa.test_point(1.0, 1.0, true); 522 | assert_relative_eq!(perimeter, 0.0); 523 | assert_relative_eq!(area, 0.0); 524 | 525 | let result = std::panic::catch_unwind(|| { 526 | let (_, _, _) = pa.test_edge(90.0, 1000.0, true); 527 | }); 528 | assert!(result.is_err()); 529 | 530 | pa.add_point(1.0, 1.0); 531 | let (perimeter, area, _) = pa.clone().compute(true); 532 | assert_relative_eq!(perimeter, 0.0); 533 | assert_relative_eq!(area, 0.0); 534 | } 535 | 536 | #[test] 537 | fn test_planimeter21() { 538 | // Copied From: https://github.com/geographiclib/geographiclib-js/blob/57137fdcf4ba56718c64b909b00331754b6efceb/geodesic/test/geodesictest.js#LL836C13-L836C13 539 | 540 | let g = Geodesic::wgs84(); 541 | 542 | let lat = 45.0; 543 | let azi = 39.2144607176828184218; 544 | let s = 8420705.40957178156285; 545 | let r = 39433884866571.4277; // Area for one circuit 546 | let a0 = g.area(); // Ellipsoid area 547 | 548 | let mut pa_clockwise = PolygonArea::new(&g, Winding::Clockwise); 549 | let mut pa_counter = PolygonArea::new(&g, Winding::CounterClockwise); 550 | 551 | pa_clockwise.add_point(lat, 60.0); 552 | pa_clockwise.add_point(lat, 180.0); 553 | pa_clockwise.add_point(lat, -60.0); 554 | pa_clockwise.add_point(lat, 60.0); 555 | pa_clockwise.add_point(lat, 180.0); 556 | pa_clockwise.add_point(lat, -60.0); 557 | 558 | pa_counter.add_point(lat, 60.0); 559 | pa_counter.add_point(lat, 180.0); 560 | pa_counter.add_point(lat, -60.0); 561 | pa_counter.add_point(lat, 60.0); 562 | pa_counter.add_point(lat, 180.0); 563 | pa_counter.add_point(lat, -60.0); 564 | 565 | for i in 3..=4 { 566 | pa_clockwise.add_point(lat, 60.0); 567 | pa_clockwise.add_point(lat, 180.0); 568 | 569 | pa_counter.add_point(lat, 60.0); 570 | pa_counter.add_point(lat, 180.0); 571 | 572 | let (_, area, _) = pa_counter.test_point(lat, -60.0, true); 573 | assert_relative_eq!(area, i as f64 * r, epsilon = 0.5); 574 | 575 | let (_, area, _) = pa_counter.test_point(lat, -60.0, false); 576 | assert_relative_eq!(area, i as f64 * r, epsilon = 0.5); 577 | 578 | let (_, area, _) = pa_clockwise.test_point(lat, -60.0, true); 579 | assert_relative_eq!(area, -i as f64 * r, epsilon = 0.5); 580 | 581 | let (_, area, _) = pa_clockwise.test_point(lat, -60.0, false); 582 | assert_relative_eq!(area, (-i as f64 * r) + a0, epsilon = 0.5); 583 | 584 | let (_, area, _) = pa_counter.test_edge(azi, s, true); 585 | assert_relative_eq!(area, i as f64 * r, epsilon = 0.5); 586 | 587 | let (_, area, _) = pa_counter.test_edge(azi, s, false); 588 | assert_relative_eq!(area, i as f64 * r, epsilon = 0.5); 589 | 590 | let (_, area, _) = pa_clockwise.test_edge(azi, s, true); 591 | assert_relative_eq!(area, -i as f64 * r, epsilon = 0.5); 592 | 593 | let (_, area, _) = pa_clockwise.test_edge(azi, s, false); 594 | assert_relative_eq!(area, (-i as f64 * r) + a0, epsilon = 0.5); 595 | 596 | pa_clockwise.add_point(lat, -60.0); 597 | pa_counter.add_point(lat, -60.0); 598 | 599 | let (_, area, _) = pa_counter.clone().compute(true); 600 | assert_relative_eq!(area, r * i as f64, epsilon = 0.5); 601 | 602 | let (_, area, _) = pa_counter.clone().compute(false); 603 | assert_relative_eq!(area, r * i as f64, epsilon = 0.5); 604 | 605 | let (_, area, _) = pa_clockwise.clone().compute(true); 606 | assert_relative_eq!(area, -(r * i as f64), epsilon = 0.5); 607 | 608 | let (_, area, _) = pa_clockwise.clone().compute(false); 609 | assert_relative_eq!(area, -(r * i as f64) + a0, epsilon = 0.5); 610 | } 611 | } 612 | 613 | #[test] 614 | fn test_planimeter29() { 615 | // Check transitdirect vs transit zero handling consistency 616 | // Copied from: https://github.com/geographiclib/geographiclib-js/blob/57137fdcf4ba56718c64b909b00331754b6efceb/geodesic/test/geodesictest.js#L883 617 | 618 | let geoid = Geodesic::wgs84(); 619 | let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise); 620 | pa.add_point(0.0, 0.0); 621 | pa.add_edge(90.0, 1000.0); 622 | pa.add_edge(0.0, 1000.0); 623 | pa.add_edge(-90.0, 1000.0); 624 | let (_, area, _) = pa.compute(true); 625 | assert_relative_eq!(area, 1000000.0, epsilon = 0.01); 626 | } 627 | } 628 | -------------------------------------------------------------------------------- /test_fixtures/GeodTest-100.dat: -------------------------------------------------------------------------------- 1 | .003311913742 0 90.001862369144 .001762385472585824 32.846794581272844 90.003358355630087731 3656488.4472191 32.957294150507910671 3458160.8183571795545 1055056257.496928 2 | 20.423135394589 0 169.895260694771 -21.179388017798691648 179.757116934570412271 10.155770628519677379 19917969.3423672 179.233700164312609141 141945.8619235957633 -113108441562692.361818 3 | 35.602540598169 0 111.870427868602 -19.406200172031696834 78.995799629955596127 126.810557548058637274 10299779.6328425 92.803714519858639493 6352380.0559844604728 10548034235226.819008 4 | 26.010745808687 0 .001066006762 64.958396828764391273 .001576658648546905 .002258602266785352 4328675.605565 38.949539053820959558 4003980.5302367959495 843422896.549356 5 | .002776960941 0 90.004934285837 -.002670966075003864 57.164518655793700538 90.00499207004449665 6363525.1342937 57.356825466117117425 5352678.9635730864489 40752767.669231 6 | .000227056052 0 90.002708094472 -.000342923983172007 176.956361997998361364 89.997304044626702539 19698692.1099486 177.551659090777928216 271551.6020163755057 -3811248736.610048 7 | 23.225828498891 0 90 -23.225828498891 179.44518683104154539 90 19975528.4919571414229 179.999999999999999999 .0000000000001 0 8 | .004857951054 0 89.99505820439 .006687014175916661 60.946393738465005896 90.001856403871962433 6784521.4815083 61.15142256193035959 5567866.1107275522269 4794483753.827583 9 | 17.162079946072 0 90 -17.162079946072 179.423239711929197618 90 19973237.8142662576352 180 0 0 10 | 72.071239718919 0 89.964343072223 -72.07123971890502904 179.813796146422114323 90.035656997118252682 20000731.2068002 179.999999977489463695 .0049842491677 50499012996.149326 11 | .003838822945 0 89.999363628335 -.00157460256166572 122.897422494858893369 90.003546844089905745 13680878.4949653 123.310860489364903276 5312358.7488895324997 2950245874.372826 12 | 89.991989548145 0 90.383140967854 -61.038150741515986969 89.631261715462284382 179.983444547483440057 16771704.5214092 150.956500662819171684 3096416.4514950950967 63475035640969.230541 13 | 18.725775553796 0 98.927919777265 18.724785210853172866 .006615636046166963 98.930043600815390181 706.2676803 .006363657651583688 706.2676788489356 1498532658.831092 14 | 89.996344967961 0 118.710939348963 -89.996002848213014514 114.609255826886658305 126.679770857297993955 20003468.6532733 179.995842549736057165 462.8051596696701 5645315274489.31877 15 | 11.748740014024 0 179.996037064439 -8.293112205271585557 .001363527637285404 179.996078816520333091 2216358.685784 19.976130176146893674 2171729.6141067742566 29450083.72759 16 | 24.171782020744 0 39.646990082748 -23.764334766744517272 179.282236094377504312 140.501874677811401335 19933995.6361023 179.472601159853402168 91910.9760307801088 71299173983021.70807 17 | 27.309748283686 0 42.992025359754 -27.199405115762465758 179.519281684356260685 137.060636244128391097 19974878.5154436 179.84950326859240369 45181.1198270686308 66497330198249.553032 18 | 58.439477249568 0 120.188884190371 -45.257027614034848134 86.24008736666370817 139.971022015956878884 13935207.2561946 125.493989533803135332 5182681.0212940816217 13991442864314.031129 19 | 89.996704955767 0 146.082536307026 -89.995999621861139779 61.27962282233654637 152.637821354333663331 20003229.2185888 179.993691670732131167 702.2398004600791 4643924365518.556242 20 | 11.280350636448 0 100.050372107063 -11.363289459370951713 178.940755072790726259 80.04316255927423735 19919776.7294766 179.524025537124153807 54766.2959447122271 -14113901283201.669656 21 | .370200182154 0 89.983622923276 -.370200182133795657 179.396506568860065718 90.016377077177663687 19970327.7613233 179.99999992955137023 .0133138527365 23100129446.975923 22 | 80.469322885334 0 105.156200975107 -80.476682352349668002 179.7382617668365442 75.0065742603772431 19999908.8578331 179.971613148836210969 3284.9211057284172 -21356222399884.772069 23 | 23.287785477695 0 87.82760486416 20.452440593866921854 35.797365608165841143 101.554180235008647064 3703998.3106422 33.36907584304041324 3498331.4083548277641 9687159334175.357207 24 | 23.694009525082 0 2.121750345489 26.198841940159034984 178.180989301629228841 177.834873388133240816 14480233.5601918 130.223414095316828878 4909775.6995088796865 124468325896717.483295 25 | 13.765992822182 0 168.128113329941 -65.459181533817945268 156.332712198928562629 28.6750940424198578 13992595.6683315 125.906798467904872903 5181734.2339005768865 -98744233934430.241325 26 | .004059463656 0 89.998424284071 .003238810845025334 63.041112122194045616 90.002903813905384588 7017704.4852368 63.253187947280765324 5676605.1521438518661 3159223713.86657 27 | 47.900780879363 0 148.758779524609 47.899309788344023215 .001327005894209747 148.759764131627358008 191.3088022 .00172115717457603 191.3088021713334 696113659.417095 28 | 89.992862960035 0 26.020973811583 67.650349673107735103 153.971406836372110945 179.991762047058247147 2495797.0749906 22.423840464512595698 2432972.8748596488837 109076670969657.203001 29 | 51.247704522328 0 17.427831845629 -50.948846967972851399 179.738248276468723375 162.687711143808350325 19967910.3438884 179.686641917368386845 59049.0219446421245 102864737847044.844787 30 | 20.32350940027 0 90 -20.32350940027 179.433892868565063777 90 19974360.7930260475772 180 0 0 31 | 53.139119113091 0 90 -53.139119113091 179.63738875327365922 90 19991796.4263024399886 180 0 0 32 | 17.285975292263 0 49.872365051814 -17.014589263992773799 179.225134874954555271 130.2262142736423542 19939465.3256469 179.580511748283521124 72112.2569738225618 56761262410452.206375 33 | 2.514600234503 0 166.362139719401 2.506446532968658234 .00196695535966103 166.362225877709852834 927.7647972 .008362236444773707 927.7647939063255 60764346.560593 34 | 89.999847105665 0 147.852478424891 -89.993713719591968804 32.889046880852145269 179.258473830612758201 20003214.918664 179.9935632120100365 716.5399431298168 22248775795087.895886 35 | 89.991811072491 0 66.114076230607 -22.591321090651902896 113.888989997384992549 179.991866881834824275 12501616.1100621 112.52648015197202269 5891503.7936166189624 80673792046469.353428 36 | 62.409951053785 0 90 -62.409951053785 179.719937697335980499 90.000000000000000002 19996692.197447633611 179.999999999999999999 .0000000000001 .000001 37 | 60.077425015363 0 81.639916890942 60.078197903525388982 .010531670881733178 81.649044728416379082 592.5766309 .005327667961870919 592.576630050364 6459158676.746339 38 | 22.86663328257 0 .000126355728 78.46255066680075319 179.999381785966101716 179.999419476069852577 8760686.5987538 78.77739996062368904 6261609.6316809521044 127515904246327.284708 39 | 28.263703662045 0 29.781655962936 -27.715946277956173917 179.384003784084706699 150.38352451336754838 19927615.2048732 179.370596162688071677 109449.1815329657556 85315421562680.160803 40 | 22.377301391995 0 158.636122297665 -58.575743237342527379 44.072498935701243344 139.846628614263799893 9845423.0412075 88.672540165795548876 6362612.1563029544463 -13282689942318.321144 41 | 18.147469450587 0 108.364591170233 -12.262527258040757617 69.795639557604029612 112.619507321054052439 8368940.3554485 75.425389380465159903 6152880.8599959222772 3001781608982.442422 42 | 36.800966049655 0 43.389649034063 36.802889529289609993 .002261122427425817 43.391003560674015305 293.7372273 .002644381079693549 293.7372271959682 956823846.539901 43 | 89.998616001454 0 95.58050104805 -89.991424540699242487 93.662700303729734082 170.756783999469465377 20002971.0360136 179.99137237706085583 960.4225652617499 53256718752864.498279 44 | 75.975352772302 0 179.997771487753 14.402365789075887732 .002021046465612995 179.999440771645567232 6842945.6235417 61.573967313834190211 5603732.2812009141173 1181317136.036532 45 | 89.991800241245 0 36.788129062927 -34.601114395238251331 143.215223003576838075 179.994020731869748208 13833040.9775294 124.517814121209784066 5255270.6662190450769 101450545716316.617618 46 | 26.813239275204 0 120.859611476541 -39.698767815933469987 117.744100212100258315 95.703240331323238135 14167447.3099024 127.632470959915860205 5039506.516023929785 -17768331207661.864041 47 | 89.990486671257 0 91.714885655236 44.98505487875443047 88.275585060623460608 179.986533008752595814 5018650.3399733 45.110870730147770262 4518742.5786654120141 62533774007075.072715 48 | 89.998386909161 0 39.911029433845 -89.994452292045323952 150.840787154433169299 169.248172444824887193 20003460.8910961 179.995772820060763008 470.5676567191029 91625597585605.158938 49 | 14.225351873668 0 90.032623328236 -14.225351873709766362 179.41491082111612486 89.967376690242821373 19972345.2829332 179.999999926862820146 .0286206020152 -46028111339.650921 50 | 2.783812471129 0 90 -2.783812471129 179.397202706910424487 90 19970405.2088365178602 180 0 0 51 | 7.226780878364 0 160.809968779511 -56.51915729310626949 33.742869850536527084 143.859731963095060454 7709737.0619647 69.43349289388461064 5958660.2903382834656 -11978952955234.757892 52 | 89.994264727937 0 161.930782982633 10.066782829989809141 18.068892784708040133 179.998187408081983337 8888115.1783194 79.960811460424029982 6280476.9636290632775 12799385925337.338665 53 | 24.138778602869 0 45.641239807379 -23.932291289258681082 179.376666336076218906 134.452253358548750152 19956935.8076368 179.70556798348106551 60157.8906273825927 62764471399155.460929 54 | 89.994923548845 0 170.802366777588 -89.998903053835312891 56.904341704514112349 132.293282908786092066 20003289.291292 179.994231312754864848 642.167249444158 -27280777507537.09188 55 | 63.66949377578 0 89.954613058191 -63.669493775736406783 179.731773933697767617 90.045387052860098319 19997291.121299 179.999999944856636723 .0144702323486 64249693867.587191 56 | 3.886251327793 0 .003365258148 46.809843909316668201 179.996182587980211992 179.99510303142707207 14388097.8595141 129.41292518697564467 4960694.2672530512559 127510534378675.636221 57 | .004023090575 0 89.996885503681 -.002055164723248576 151.123219293604579159 90.004645596608656255 16822959.8159264 151.631611349310522139 3020339.7629593202216 5472866685.872444 58 | 38.307529776271 0 125.010280831823 -38.366787958630341372 179.504274964038264957 55.056425447047212133 19978550.0539817 179.896707628849581478 25095.0762010182431 -49451926554249.580459 59 | 25.575488903661 0 179.99856564678 -42.739745780540142188 .001804651118343673 179.998239999472755229 7563613.4080721 68.144483186103658276 5902829.5684710624656 -230012043.822694 60 | 22.5638815461 0 92.656027197162 3.504601673544211816 74.442871731040704334 112.386947176927138921 8268776.6328299 74.510308236012646787 6128471.472760805008 13921383920403.241935 61 | 22.367284344899 0 179.996666424849 -36.938244357766476763 .003565430381119167 179.996145882523051032 6564128.4134201 59.145508485260206023 5459113.6332448904118 -367544692.248714 62 | 37.642327604657 0 90 -37.642327604657 179.521672254586070255 90.000000000000000001 19982817.927991328007 179.999999999999999999 .0000000000001 0 63 | 89.994940622706 0 96.907186071374 -89.994914740335306689 164.093193142186758948 98.99956768697606414 20003774.649007 179.998591357563020376 156.8093488111749 1482294346678.490717 64 | 89.998234417195 0 99.176774580899 -89.996900935085568573 115.046731074430786869 145.776475895952549486 20003613.796678 179.997146394455146006 317.6619011929152 33012368921195.078726 65 | 63.655969559901 0 65.042796414527 12.60943957803960895 111.700018655530895272 155.586853390157959859 9790058.7849835 88.08247324845992227 6376574.3477795014918 64064866938432.523903 66 | 59.801956595509 0 63.92337109715 -59.729524672336685617 179.435039799382973226 116.328737198856636806 19978769.1563271 179.835688485107169729 21611.9645236803343 37088630464387.840628 67 | 56.310719132311 0 155.158323803365 56.304258387808388716 .005380024608319352 155.162800127144988655 792.7231052 .00712853347104936 792.7231031643268 3166757036.355314 68 | 77.456110133819 0 104.471926587063 -44.264948015173204867 87.19591715273438267 162.895177828292883001 14692937.4790901 132.271399734201557653 4717065.5278953619327 41364316765169.253487 69 | 89.992870660652 0 2.312121073176 22.2343513883473544 177.687760223593597281 179.999688379712642505 7543010.742692 67.84011396578144845 5907018.1440118588669 125878220669062.959231 70 | 89.999172440969 0 92.073657163691 10.980439868636816382 87.926178538128453996 179.999154828344867351 8787660.0016896 79.055454954967319222 6262127.6164360030171 62288571909831.057439 71 | 89.992968073625 0 87.213646671406 51.11230323311336536 92.777629012592921669 179.988797418894838162 4337424.4660706 38.982103210919230637 4012343.2517916614161 65724173395570.491707 72 | .002812064563 0 89.996294401198 .002513707013984988 109.898472511488134325 90.003912775149534572 12233841.9793916 110.268180666215694308 5963151.5012014573172 5372918261.208087 73 | .002214721753 0 90.004938778767 .001604859709652937 6.860305943586887695 90.005167722849785647 763685.766878 6.883384651955295924 761850.0367143784825 161464618.434075 74 | .001703113279 0 89.998205762628 -.000097841937049584 138.385711777345784609 90.002467986212309973 15405026.9654032 138.851253716710097812 4182845.5371487106558 3005966768.603361 75 | 89.997429560505 0 178.514819864887 -65.276086928882025457 1.485324213672079264 179.999840618069374415 17243800.0650818 155.200325419504625617 2675288.8695852385016 1052024958156.601798 76 | 26.261588360663 0 179.995386740395 -39.572703865499621083 .005429770651010584 179.994636624201196162 7287902.0829884 65.663555530308027491 5794412.1523505828548 -529755741.280425 77 | 48.135086661105 0 73.505128675293 -48.042240442127846927 179.15041231458295954 106.839330708214020348 19954134.8053171 179.676167309795666303 38469.8677033954896 23570130016527.185084 78 | 49.016921879693 0 89.97060777642 -49.016921879689829285 179.603631159825272651 90.029392230673919499 19989432.2401999 179.999999993816293654 .0083227454121 41564004594.283611 79 | 89.997659406498 0 145.374216128769 -89.998339938457689481 87.865826851456817992 126.759942941884937527 20003605.3662688 179.997070662683442828 326.0922989089565 -13186806702722.774783 80 | 69.91995712112 0 89.966532300422 -69.91995712111389255 179.792335960763673918 90.033467728157922983 19999951.0726153 179.999999989517371931 .0038830639169 47393576692.851575 81 | 61.008503581341 0 90 -61.008503581341 179.706928937552694972 90 19996004.125969068802 180 0 0 82 | 60.960853683623 0 125.969562235596 -61.056831724781232912 179.488837476780395043 54.269782677914396023 19980463.0288434 179.83582993051254271 23699.3406098785241 -50753228071620.187693 83 | 89.990500336545 0 134.946410672082 -89.993072781422511179 121.125690626167207128 103.92782721119373129 20002995.6448246 179.991593445775422691 935.8131018696673 -21974323689751.265452 84 | .001379365755 0 89.998608137015 .000753702665148418 112.396451668205406084 90.001806358366220812 12511915.7635091 112.774563398319462624 5861148.7509013045648 2255570809.512947 85 | .001502302645 0 89.995375331304 .002016933385417558 137.172078202036272595 90.004425898524100302 15269925.8811879 137.633537253837755152 4283624.8602062305173 6382983849.378608 86 | 39.467536559447 0 52.043751751992 6.778367817013944654 135.413534116455116183 142.136541627505064921 13145044.4390506 118.318018254638677433 5624327.3976033117245 63684702532912.381707 87 | 8.849455388939 0 129.673344478389 8.843107684651578467 .007694428892432052 129.674527758774555025 1099.6867173 .009911101886990484 1099.6867118166296 834605908.55832 88 | 89.997741785164 0 54.490356865324 -89.996380304433206558 156.029698677757598294 149.47992501675096329 20003729.6800718 179.998187392724943339 201.7786030269593 67292934911297.598895 89 | 37.634753648535 0 .002089246356 61.890738855282764565 179.995628232403445455 179.996493158790602541 8972218.8832628 80.647560489098724547 6304613.8296483476728 127512434391967.149844 90 | 36.506650377531 0 107.590343326974 -36.588407792716013941 179.215726385985210555 72.601092760702380858 19953986.6219008 179.72830173369568966 34109.6932163783445 -24719954402446.118312 91 | 89.999897452402 0 96.361176350804 -89.995532474115347144 84.94600621044379487 178.692816358561460418 20003431.3236943 179.995507211473227969 500.1349294849825 58325748816808.988113 92 | 49.846791280566 0 74.609534062637 -49.805267838008557655 179.392474036203974879 105.567564984959507498 19973596.3231161 179.844328221192993691 19304.5071765506116 21892448906527.861686 93 | 11.599421564288 0 .001972605932 2.711886130346250063 179.999494689202461349 179.998065266246213418 18421292.1732589 165.735613068572138508 1632032.0912230202918 127513625272993.900066 94 | 51.100294727211 0 47.222519143362 -51.021510167650203128 179.586580802013682521 132.882384482574663082 19983877.5458822 179.884029385265276483 25203.2037483614391 60611828286433.61737 95 | 53.483892345764 0 142.52471991661 -18.960904210068101428 39.172919840228105238 157.449049974579802578 8861654.1189795 79.820644367881779831 6262656.4247476698712 10546405425464.885156 96 | 62.908289108067 0 .002909387719 25.5391837823150997 179.996771867442674754 179.998528540173104637 10199985.7781705 91.705396517412105207 6389405.0974874784875 127513296215305.549257 97 | 89.997121349605 0 66.596353832361 -89.989768047293380893 128.36650593520996481 165.037112133961371894 20002955.0741735 179.991228989881318332 976.3846089937235 69737842479069.616941 98 | .001219589977 0 90.001453909439 -.001481952497346118 168.126791394761751635 89.998812644101188609 18715788.8067849 168.69238502213376778 1246409.5704417916925 -1862773192.315824 99 | 11.898997917427 0 75.119044134969 11.899065303345823952 .000257501685735042 75.119097228635683489 29.0265121 .000261589547493973 29.0265120998992 37451833.144851 100 | 89.993552102933 0 125.229972203857 -89.994733015409121247 144.488804488957651172 90.281167517752702432 20003513.1221078 179.996242021687723698 418.3362298439258 -24758588597156.51604 101 | --------------------------------------------------------------------------------