#![allow(clippy::expect_used, clippy::unwrap_used)]
use core::f64::consts::PI;
use kinavis::sailings::{geodesic, great_circle, rhumb_destination, rhumb_line, EARTH_RADIUS};
use kinavis::{Distance, Latitude, Longitude, Position, TrueCourse};
const WGS84_A: f64 = 6_378_137.0;
fn dms(sign: f64, degrees: f64, minutes: f64, seconds: f64) -> f64 {
sign * (degrees + minutes / 60.0 + seconds / 3600.0)
}
fn at(latitude: f64, longitude: f64) -> Position {
Position::new(
Latitude::from_degrees(latitude).expect("in range"),
Longitude::from_degrees(longitude).expect("in range"),
)
}
#[test]
fn vincentys_published_line() {
let flinders = at(
dms(-1.0, 37.0, 57.0, 3.720_30),
dms(1.0, 144.0, 25.0, 29.524_40),
);
let buninyong = at(
dms(-1.0, 37.0, 39.0, 10.156_10),
dms(1.0, 143.0, 55.0, 35.383_90),
);
let sailing = geodesic(flinders, buninyong).expect("a short, ordinary line");
assert!(
(sailing.distance.metres() - 54_972.271).abs() < 5e-4,
"{} m",
sailing.distance.metres()
);
let alpha1 = dms(1.0, 306.0, 52.0, 5.37);
assert!(
(sailing.initial_course.degrees() - alpha1).abs() < 3e-6,
"{} vs {alpha1}",
sailing.initial_course.degrees()
);
let alpha2 = dms(1.0, 127.0, 10.0, 25.07);
assert!(
(sailing.final_course.reciprocal().degrees() - alpha2).abs() < 3e-6,
"{} vs {alpha2}",
sailing.final_course.reciprocal().degrees()
);
let back = geodesic(buninyong, flinders).expect("the same line, reversed");
assert!(
back.initial_course
.angular_distance(sailing.final_course.reciprocal())
< 1e-9
);
}
#[test]
fn wgs84_arcs_match_the_published_figures() {
let equator = geodesic(at(0.0, 0.0), at(0.0, 1.0)).expect("along the equator");
let exact = WGS84_A * PI / 180.0;
assert!(
(equator.distance.metres() - exact).abs() < 1e-6,
"{} vs {exact}",
equator.distance.metres()
);
let quarter = geodesic(at(0.0, 0.0), at(90.0, 0.0)).expect("up the meridian");
assert!(
(quarter.distance.metres() - 10_001_965.729).abs() < 1e-3,
"{} m",
quarter.distance.metres()
);
let first = geodesic(at(0.0, 0.0), at(1.0, 0.0)).expect("up the meridian");
assert!(
(first.distance.metres() - 110_574.389).abs() < 1e-2,
"{} m",
first.distance.metres()
);
let last = geodesic(at(89.0, 0.0), at(90.0, 0.0)).expect("up the meridian");
assert!(
(last.distance.metres() - 111_693.865).abs() < 1e-2,
"{} m",
last.distance.metres()
);
}
#[test]
fn the_sphere_gives_exact_answers_in_places() {
let quarter = EARTH_RADIUS.metres() * PI / 2.0;
let along = great_circle(at(0.0, 0.0), at(0.0, 90.0)).expect("along the equator");
assert!((along.distance.metres() - quarter).abs() < 1e-6);
let up = great_circle(at(0.0, 0.0), at(90.0, 0.0)).expect("up the meridian");
assert!((up.distance.metres() - quarter).abs() < 1e-6);
let across = great_circle(at(0.0, 0.0), at(45.0, 90.0)).expect("a general line");
assert!(
(across.distance.metres() - quarter).abs() < 1e-6,
"{} vs {quarter}",
across.distance.metres()
);
}
#[test]
fn rhumb_lines_agree_with_plane_trigonometry() {
fn ten_degrees() -> f64 {
EARTH_RADIUS.metres() * (10.0_f64).to_radians() / 1852.0
}
let east = rhumb_line(at(0.0, 0.0), at(0.0, 10.0)).expect("along the equator");
assert!((east.initial_course.degrees() - 90.0).abs() < 1e-9);
assert!(
(east.distance.nautical_miles() - ten_degrees()).abs() < 1e-9,
"{} vs {}",
east.distance.nautical_miles(),
ten_degrees()
);
let north = rhumb_line(at(0.0, 0.0), at(10.0, 0.0)).expect("up the meridian");
assert!(north.initial_course.degrees().abs() < 1e-9);
assert!((north.distance.nautical_miles() - ten_degrees()).abs() < 1e-9);
let run = 100.0;
let arrival = rhumb_destination(
at(0.0, 0.0),
TrueCourse::new(45.0).expect("in range"),
Distance::from_nautical_miles(run).expect("finite"),
)
.expect("a short leg from the equator");
let expected_radians = run * 1852.0 * (45.0_f64).to_radians().cos() / EARTH_RADIUS.metres();
let northing = arrival.latitude().degrees().to_radians();
assert!(
(northing - expected_radians).abs() < 1e-12,
"{northing} vs {expected_radians}"
);
}