1use geometry_strategy::{ConvexHullStrategy, MonotoneChain};
12use geometry_trait::Point;
13
14#[must_use]
22pub fn convex_hull<G, P>(g: &G) -> geometry_model::Ring<P, true, true>
23where
24 MonotoneChain: ConvexHullStrategy<G, Output = geometry_model::Ring<P, true, true>>,
25 P: Point,
26{
27 MonotoneChain.convex_hull(g)
28}
29
30#[cfg(test)]
31#[allow(
32 clippy::float_cmp,
33 reason = "Hull corner coordinates are exact literals."
34)]
35mod tests {
36 use super::convex_hull;
42 use geometry_cs::Cartesian;
43 use geometry_model::{MultiPoint, Point2D, Polygon, polygon};
44 use geometry_trait::{Point as _, Ring as _};
45
46 type Pt = Point2D<f64, Cartesian>;
47
48 #[test]
49 fn square_plus_interior_point_hull_has_four_corners() {
50 let mp = MultiPoint(alloc::vec![
51 Pt::new(0., 0.),
52 Pt::new(4., 0.),
53 Pt::new(4., 4.),
54 Pt::new(0., 4.),
55 Pt::new(2., 2.),
56 ]);
57 let hull = convex_hull(&mp);
58 assert_eq!(hull.points().count(), 5);
60 assert_eq!(
62 hull.0.first().unwrap().get::<0>(),
63 hull.0.last().unwrap().get::<0>()
64 );
65 assert_eq!(
66 hull.0.first().unwrap().get::<1>(),
67 hull.0.last().unwrap().get::<1>()
68 );
69 }
70
71 #[test]
72 fn concave_polygon_hull_drops_reflex_vertex() {
73 let pg: Polygon<Pt> =
76 polygon![[(0., 0.), (4., 0.), (2., 1.), (4., 4.), (0., 4.), (0., 0.)]];
77 let hull = convex_hull(&pg);
78 assert_eq!(hull.points().count(), 5);
80 let has_reflex = hull
81 .0
82 .iter()
83 .any(|p| p.get::<0>() == 2.0 && p.get::<1>() == 1.0);
84 assert!(!has_reflex);
85 }
86
87 #[test]
90 fn hull_of_fewer_than_three_points_is_the_input_closed() {
91 let empty = MultiPoint::<Pt>(alloc::vec![]);
92 assert_eq!(convex_hull(&empty).points().count(), 0);
93 let one = MultiPoint(alloc::vec![Pt::new(3., 4.)]);
94 let hull = convex_hull(&one);
95 assert_eq!(hull.points().count(), 2);
96 assert_eq!(hull.0[0].get::<0>(), 3.0);
97 assert_eq!(hull.0[1].get::<0>(), 3.0);
98 let two = MultiPoint(alloc::vec![Pt::new(0., 0.), Pt::new(1., 1.)]);
101 let hull = convex_hull(&two);
102 let pts: alloc::vec::Vec<(f64, f64)> = hull
103 .0
104 .iter()
105 .map(|p| (p.get::<0>(), p.get::<1>()))
106 .collect();
107 assert_eq!(pts, alloc::vec![(0., 0.), (1., 1.), (0., 0.)]);
108 }
109
110 #[test]
112 fn hull_of_linestring() {
113 use geometry_model::{Linestring, linestring};
114 let ls: Linestring<Pt> = linestring![(0., 0.), (4., 0.), (4., 4.), (2., 2.)];
115 let hull = convex_hull(&ls);
116 assert_eq!(hull.points().count(), 4);
118 }
119
120 #[test]
122 fn hull_of_ring() {
123 use geometry_model::Ring;
124 let r: Ring<Pt> = Ring::from_vec(alloc::vec![
125 Pt::new(0., 0.),
126 Pt::new(4., 0.),
127 Pt::new(2., 1.), Pt::new(4., 4.),
129 Pt::new(0., 4.),
130 Pt::new(0., 0.),
131 ]);
132 let hull = convex_hull(&r);
133 assert_eq!(hull.points().count(), 5); assert!(
135 !hull
136 .0
137 .iter()
138 .any(|p| p.get::<0>() == 2.0 && p.get::<1>() == 1.0)
139 );
140 }
141
142 #[test]
145 fn hull_of_polygon_ignores_holes() {
146 let pg: Polygon<Pt> = polygon![
147 [(0., 0.), (4., 0.), (4., 4.), (0., 4.), (0., 0.)],
148 [(1., 1.), (2., 1.), (2., 2.), (1., 2.), (1., 1.)],
149 ];
150 let hull = convex_hull(&pg);
151 assert_eq!(hull.points().count(), 5);
152 assert!(
153 !hull
154 .0
155 .iter()
156 .any(|p| p.get::<0>() == 1.0 && p.get::<1>() == 1.0)
157 );
158 }
159
160 #[test]
163 fn hull_is_wound_clockwise() {
164 use crate::area::ring_area;
165 let mp = MultiPoint(alloc::vec![
166 Pt::new(0., 0.),
167 Pt::new(4., 0.),
168 Pt::new(4., 4.),
169 Pt::new(0., 4.),
170 Pt::new(2., 2.),
171 ]);
172 assert_eq!(ring_area(&convex_hull(&mp)), 16.0);
173 let pg: Polygon<Pt> =
174 polygon![[(0., 0.), (4., 0.), (2., 1.), (4., 4.), (0., 4.), (0., 0.)]];
175 assert_eq!(ring_area(&convex_hull(&pg)), 16.0);
176 }
177}