1use geometry_strategy::{CartesianClosestPoints, ClosestPointsStrategy};
15
16#[inline]
31#[must_use]
32pub fn closest_points<A, B>(
33 a: &A,
34 b: &B,
35) -> (
36 <CartesianClosestPoints as ClosestPointsStrategy<A, B>>::Out,
37 <CartesianClosestPoints as ClosestPointsStrategy<A, B>>::Out,
38)
39where
40 CartesianClosestPoints: ClosestPointsStrategy<A, B>,
41{
42 CartesianClosestPoints.closest_points(a, b)
43}
44
45#[cfg(test)]
46#[allow(
47 clippy::float_cmp,
48 reason = "Closest-point coordinates are exact for these inputs."
49)]
50mod tests {
51 use super::closest_points;
58 use geometry_cs::Cartesian;
59 use geometry_model::{Linestring, Point2D, Segment};
60 use geometry_strategy::{DistanceStrategy, Pythagoras};
61 use geometry_trait::Point as _;
62
63 type Pt = Point2D<f64, Cartesian>;
64
65 #[test]
66 fn point_above_segment_drops_perpendicular() {
67 let p = Pt::new(0., 5.);
69 let s = Segment::new(Pt::new(0., 0.), Pt::new(10., 0.));
70 let (a, b) = closest_points(&p, &s);
71 assert_eq!((a.get::<0>(), a.get::<1>()), (0., 5.));
72 assert_eq!((b.get::<0>(), b.get::<1>()), (0., 0.));
73 assert!((Pythagoras.distance(&a, &b) - 5.0).abs() < 1e-12);
74 }
75
76 #[test]
77 fn point_on_segment_returns_input() {
78 let p = Pt::new(1., 1.);
79 let s = Segment::new(Pt::new(0., 0.), Pt::new(3., 3.));
80 let (a, b) = closest_points(&p, &s);
81 assert!(Pythagoras.distance(&a, &b) < 1e-12);
82 }
83
84 #[test]
85 fn crossing_segments_share_intersection_point() {
86 let a = Segment::new(Pt::new(0., 0.), Pt::new(2., 2.));
88 let b = Segment::new(Pt::new(0., 2.), Pt::new(2., 0.));
89 let (ca, cb) = closest_points(&a, &b);
90 assert!((ca.get::<0>() - 1.0).abs() < 1e-12 && (ca.get::<1>() - 1.0).abs() < 1e-12);
91 assert!(Pythagoras.distance(&ca, &cb) < 1e-12);
92 }
93
94 #[test]
95 fn parallel_linestrings_closest_pair() {
96 let a: Linestring<Pt> =
99 Linestring::from_vec(alloc::vec![Pt::new(0., 0.), Pt::new(10., 0.),]);
100 let b: Linestring<Pt> =
101 Linestring::from_vec(alloc::vec![Pt::new(2., 3.), Pt::new(8., 3.),]);
102 let (ca, cb) = closest_points(&a, &b);
103 assert!((Pythagoras.distance(&ca, &cb) - 3.0).abs() < 1e-9);
104 }
105
106 #[test]
107 #[should_panic(expected = "empty or degenerate linestring in closest_points")]
108 fn degenerate_linestring_panics() {
109 let a: Linestring<Pt> = Linestring::from_vec(alloc::vec![Pt::new(0., 0.)]);
110 let b: Linestring<Pt> =
111 Linestring::from_vec(alloc::vec![Pt::new(0., 0.), Pt::new(1., 0.),]);
112 let _ = closest_points(&a, &b);
113 }
114
115 #[test]
117 fn point_point_returns_the_inputs() {
118 let a = Pt::new(1., 2.);
119 let b = Pt::new(4., 6.);
120 let (ca, cb) = closest_points(&a, &b);
121 assert_eq!((ca.get::<0>(), ca.get::<1>()), (1., 2.));
122 assert_eq!((cb.get::<0>(), cb.get::<1>()), (4., 6.));
123 assert!((Pythagoras.distance(&ca, &cb) - 5.0).abs() < 1e-12);
124 }
125
126 #[test]
129 fn point_point_in_three_dimensions() {
130 use geometry_model::Point3D;
131 type P3 = Point3D<f64, Cartesian>;
132 let a = P3::new(0., 0., 0.);
133 let b = P3::new(1., 2., 2.);
134 let (ca, cb) = closest_points(&a, &b);
135 assert_eq!((ca.get::<0>(), ca.get::<1>(), ca.get::<2>()), (0., 0., 0.));
136 assert_eq!((cb.get::<0>(), cb.get::<1>(), cb.get::<2>()), (1., 2., 2.));
137 }
138
139 #[test]
143 fn parallel_segments_closest_pair_via_endpoint_projection() {
144 let a = Segment::new(Pt::new(0., 0.), Pt::new(4., 0.));
145 let b = Segment::new(Pt::new(0., 1.), Pt::new(4., 1.));
146 let (ca, cb) = closest_points(&a, &b);
147 assert!((Pythagoras.distance(&ca, &cb) - 1.0).abs() < 1e-12);
148 }
149
150 #[test]
154 fn degenerate_segment_projects_to_its_point() {
155 let p = Pt::new(3., 4.);
156 let s = Segment::new(Pt::new(0., 0.), Pt::new(0., 0.));
158 let (a, b) = closest_points(&p, &s);
159 assert_eq!((a.get::<0>(), a.get::<1>()), (3., 4.));
160 assert_eq!((b.get::<0>(), b.get::<1>()), (0., 0.));
161 assert!((Pythagoras.distance(&a, &b) - 5.0).abs() < 1e-12);
162 }
163
164 #[test]
168 fn linestring_linestring_closest_over_all_segment_pairs() {
169 use geometry_model::linestring;
170 let a: Linestring<Pt> = linestring![(0., 0.), (4., 0.)];
171 let b: Linestring<Pt> = linestring![(0., 3.), (4., 3.)];
172 let (ca, cb) = closest_points(&a, &b);
173 assert!((Pythagoras.distance(&ca, &cb) - 3.0).abs() < 1e-12);
174 }
175}