use super::{Vector3, calculate_great_circle_atd, normalise, normalise_centroid, sq_distance};
use angle_sc::{Angle, Radians};
use num_traits::{Float, float::FloatConst};
#[must_use]
pub fn calculate_intersection<T>(
pole_0: &Vector3<T>,
pole_1: &Vector3<T>,
min_sq_value: T,
) -> Option<Vector3<T>>
where
T: Float + na::Scalar + na::ComplexField<RealField = T>,
{
normalise(&pole_0.cross(pole_1), min_sq_value)
}
#[must_use]
pub fn use_antipodal_point<T>(point: &Vector3<T>, centroid: &Vector3<T>) -> bool
where
T: Float + na::Scalar + na::ComplexField<RealField = T>,
{
sq_distance(centroid, &(-*point)) < sq_distance(centroid, point)
}
#[must_use]
pub fn closest_intersection_point<T>(point: &Vector3<T>, centroid: &Vector3<T>) -> Vector3<T>
where
T: Float + na::Scalar + na::ComplexField<RealField = T>,
{
if use_antipodal_point(point, centroid) {
-*point
} else {
*point
}
}
#[must_use]
pub fn calculate_reference_point_and_angle<T>(
mid_point_0: &Vector3<T>,
pole_0: &Vector3<T>,
mid_point_1: &Vector3<T>,
pole_1: &Vector3<T>,
sq_sin_max_coincident_angle: T,
) -> (Vector3<T>, Angle<T>)
where
T: Float + na::Scalar + na::ComplexField<RealField = T>,
f64: From<T>,
{
let centroid = mid_point_0 + mid_point_1;
let point = pole_0.cross(pole_1);
normalise(&point, sq_sin_max_coincident_angle).map_or_else(
|| {
let c = normalise_centroid(¢roid, mid_point_0, pole_0);
(c, Angle::from_y_x(point.norm(), pole_0.dot(pole_1)))
},
|p| {
let x = closest_intersection_point(&p, ¢roid);
(x, Angle::from_y_x(point.norm(), pole_0.dot(pole_1)))
},
)
}
#[must_use]
pub fn calculate_arc_reference_distances_and_angle<T>(
mid_point_0: &Vector3<T>,
pole_0: &Vector3<T>,
mid_point_1: &Vector3<T>,
pole_1: &Vector3<T>,
sq_sin_max_coincident_angle: T,
) -> (Radians<T>, Radians<T>, Angle<T>)
where
T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
f64: From<T>,
{
let (point, angle) = calculate_reference_point_and_angle(
mid_point_0,
pole_0,
mid_point_1,
pole_1,
sq_sin_max_coincident_angle,
);
let distance_0 = calculate_great_circle_atd(mid_point_0, pole_0, &point);
let distance_1 = calculate_great_circle_atd(mid_point_1, pole_1, &point);
(distance_0, distance_1, angle)
}
#[cfg(test)]
mod tests {
use std::f64;
use super::*;
use crate::{LatLong, vector};
use angle_sc::{Angle, Degrees, is_within_tolerance};
pub const MIN_SQ_NORM: f64 = vector::tests::MIN_SQ_NORM;
#[test]
fn test_calculate_intersection() {
let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(0.0));
let south_pole = Vector3::from(&lat_lon_south);
let lat_lon_north = LatLong::new(Degrees(90.0), Degrees(0.0));
let north_pole = Vector3::from(&lat_lon_north);
let lat_lon_idl = LatLong::new(Degrees(0.0), Degrees(180.0));
let idl = Vector3::from(&lat_lon_idl);
let equator_intersection = calculate_intersection(&south_pole, &north_pole, MIN_SQ_NORM);
assert!(equator_intersection.is_none());
let gc_intersection1 = calculate_intersection(&idl, &north_pole, MIN_SQ_NORM).unwrap();
let gc_intersection2 = calculate_intersection(&idl, &south_pole, MIN_SQ_NORM).unwrap();
assert_eq!(gc_intersection1, -gc_intersection2);
}
#[test]
fn test_calculate_arc_reference_distances_and_angle_coincident_great_circles() {
let point_1 = Vector3::new(1.0, 0.0, 0.0);
let pole_1 = Vector3::new(0.0, 0.0, 1.0);
let result = calculate_arc_reference_distances_and_angle(
&point_1,
&pole_1,
&point_1,
&pole_1,
MIN_SQ_NORM,
);
assert_eq!(Radians(0.0), result.0);
assert_eq!(Radians(0.0), result.1);
assert_eq!(Degrees(0.0), Degrees::from(result.2));
let point_m1 = -point_1;
let result = calculate_arc_reference_distances_and_angle(
&point_1,
&pole_1,
&point_m1,
&pole_1,
MIN_SQ_NORM,
);
assert!(is_within_tolerance(
-f64::consts::FRAC_PI_2,
result.0.0,
f64::EPSILON
));
assert!(is_within_tolerance(
f64::consts::FRAC_PI_2,
result.1.0,
f64::EPSILON
));
assert_eq!(Degrees(0.0), Degrees::from(result.2));
let pole_m1 = -pole_1;
let result = calculate_arc_reference_distances_and_angle(
&point_1,
&pole_1,
&point_m1,
&pole_m1,
MIN_SQ_NORM,
);
assert!(is_within_tolerance(
-f64::consts::FRAC_PI_2,
result.0.0,
f64::EPSILON
));
assert!(is_within_tolerance(
-f64::consts::FRAC_PI_2,
result.1.0,
f64::EPSILON
));
assert_eq!(Degrees(180.0), Degrees::from(result.2));
}
#[test]
fn test_calculate_arc_reference_distances_and_angle_intersecting_great_circles() {
let point_1 = Vector3::new(1.0, 0.0, 0.0);
let pole_1 = Vector3::new(0.0, 0.0, 1.0);
let pole_2 = Vector3::new(0.0, 1.0, 0.0);
let result = calculate_arc_reference_distances_and_angle(
&point_1,
&pole_1,
&point_1,
&pole_2,
MIN_SQ_NORM,
);
assert_eq!(Radians(0.0), result.0);
assert_eq!(Radians(0.0), result.1);
assert_eq!(Degrees(90.0), Degrees::from(result.2));
let pole_3 = (pole_1 + pole_2).normalize();
let result = calculate_arc_reference_distances_and_angle(
&point_1,
&pole_1,
&point_1,
&pole_3,
MIN_SQ_NORM,
);
assert_eq!(Radians(0.0), result.0);
assert_eq!(Radians(0.0), result.1);
assert_eq!(Degrees(45.0), Degrees::from(result.2));
let pole_m3 = -pole_3;
let result = calculate_arc_reference_distances_and_angle(
&point_1,
&pole_1,
&point_1,
&pole_m3,
MIN_SQ_NORM,
);
assert_eq!(Radians(0.0), result.0);
assert_eq!(Radians(0.0), result.1);
assert_eq!(Degrees(135.0), Degrees::from(result.2));
let point_2 = vector::position(
&point_1,
&vector::direction(&point_1, &pole_3),
Angle::default().quarter_turn_cw(),
);
let result = calculate_arc_reference_distances_and_angle(
&point_1,
&pole_1,
&point_2,
&pole_3,
MIN_SQ_NORM,
);
assert_eq!(Radians(0.0), result.0);
assert!(is_within_tolerance(
-f64::consts::FRAC_PI_2,
result.1.0,
f64::EPSILON
));
assert_eq!(Degrees(45.0), Degrees::from(result.2));
let result = calculate_arc_reference_distances_and_angle(
&point_1,
&pole_1,
&point_2,
&pole_m3,
MIN_SQ_NORM,
);
assert_eq!(Radians(0.0), result.0);
assert!(is_within_tolerance(
f64::consts::FRAC_PI_2,
result.1.0,
f64::EPSILON
));
assert_eq!(Degrees(135.0), Degrees::from(result.2));
}
}