use crate::{Angle, NVector, Vec3};
use super::base::easting;
#[derive(PartialEq, Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GreatCircle {
normal: Vec3,
}
impl GreatCircle {
pub fn new(p1: NVector, p2: NVector) -> Self {
let normal = p1.as_vec3().orthogonal_to(p2.as_vec3());
GreatCircle { normal }
}
pub fn from_heading(p: NVector, bearing: Angle) -> Self {
let e = easting(p.as_vec3());
let n = p.as_vec3().cross_prod(e);
let b_rads = bearing.as_radians();
let se = e * (b_rads.cos() / e.norm());
let sn = n * (b_rads.sin() / n.norm());
let normal = sn - se;
GreatCircle { normal }
}
#[inline]
pub fn normal(&self) -> Vec3 {
self.normal
}
pub fn projection(&self, p: NVector) -> Option<NVector> {
let n1 = self.normal;
let n2 = p.as_vec3().stable_cross_prod_unit(n1);
if n2 == Vec3::ZERO {
Some(NVector::new(p.as_vec3().orthogonal()))
} else {
let proj = n1.orthogonal_to(n2);
Some(NVector::new(proj))
}
}
}
#[cfg(test)]
mod tests {
use crate::{positions::assert_opt_nv_eq_d7, spherical::GreatCircle, Angle, NVector, Vec3};
#[test]
fn projection_nearly_perpendicular_null_island() {
let start = NVector::from_lat_long_degrees(80.0, -90.0);
let end = NVector::from_lat_long_degrees(80.0, 90.0);
assert_opt_nv_eq_d7(
NVector::new(Vec3::UNIT_Z),
GreatCircle::new(start, end).projection(NVector::from_lat_long_degrees(0.0, 0.0)),
);
}
#[test]
fn from_heading() {
let null_island = NVector::from_lat_long_degrees(0.0, 0.0);
let a = GreatCircle::from_heading(null_island, Angle::from_degrees(90.0));
let e = GreatCircle::new(null_island, NVector::from_lat_long_degrees(0.0, 10.0));
let d = e.normal - a.normal;
let n = d.norm();
assert!(n.abs() < 1e-15);
}
}