rgeometry/algorithms/convex_hull/
graham_scan.rs1use crate::data::{Point, Polygon, PolygonConvex};
2use crate::{Error, Orientation, PolygonScalar, TotalOrd};
3
4pub fn convex_hull<T>(mut pts: Vec<Point<T>>) -> Result<PolygonConvex<T>, Error>
80where
81 T: PolygonScalar,
82{
83 let smallest = &smallest_point(&pts)?;
84
85 pts.sort_unstable_by(|a, b| {
86 smallest
87 .ccw_cmp_around(a, b)
88 .then_with(|| (a.y_coord(), a.x_coord()).total_cmp(&(b.y_coord(), b.x_coord())))
89 });
91 if pts.len() < 3 {
92 return Err(Error::InsufficientVertices);
93 }
94 debug_assert_eq!(&pts[0], smallest);
95 let mut write_idx = 1;
96 let mut read_idx = 2;
97 {
99 let origin = pts[write_idx - 1].clone();
100 while read_idx < pts.len() {
101 if Point::orient(&origin, &pts[write_idx], &pts[read_idx]) == Orientation::CoLinear {
102 pts.swap(read_idx, write_idx);
103 read_idx += 1;
104 } else {
105 break;
106 }
107 }
108 }
109 while read_idx < pts.len() {
111 let p1 = &pts[read_idx];
112 let p2 = &pts[write_idx];
113 let p3 = &pts[write_idx - 1];
114 match Point::orient(p3, p2, p1) {
115 Orientation::CounterClockWise => {
116 pts.swap(read_idx, write_idx + 1);
117 read_idx += 1;
118 write_idx += 1;
119 }
120 Orientation::ClockWise | Orientation::CoLinear => {
121 write_idx -= 1;
122 }
123 }
124 }
125 pts.truncate(write_idx + 1);
126 if pts.len() < 3 {
127 return Err(Error::InsufficientVertices);
128 }
129 Ok(PolygonConvex::new_unchecked(Polygon::new_unchecked(pts)))
130}
131
132fn smallest_point<T>(pts: &[Point<T>]) -> Result<Point<T>, Error>
135where
136 T: PolygonScalar,
137{
138 Ok(
139 pts
140 .iter()
141 .min_by(|a, b| TotalOrd::total_cmp(&(a.y_coord(), a.x_coord()), &(b.y_coord(), b.x_coord())))
142 .ok_or(Error::InsufficientVertices)?
143 .clone(),
144 )
145}
146
147#[cfg(test)]
148#[cfg(not(tarpaulin_include))]
149mod tests {
150 use super::*;
151 use crate::data::PointLocation;
152 use crate::testing::*;
153
154 use claims::assert_ok;
155 use num_bigint::BigInt;
156
157 use proptest::collection::*;
158 use proptest::prelude::*;
159 use test_strategy::proptest;
160
161 #[test]
162 fn convex_hull_colinear() {
163 let points = vec![
164 Point::new([0, 0]),
165 Point::new([1, 0]),
166 Point::new([2, 0]),
167 Point::new([3, 0]),
168 Point::new([4, 0]),
169 Point::new([1, 1]),
170 ];
171 let poly = convex_hull(points).unwrap();
172 assert_ok!(poly.validate());
173 }
174
175 #[test]
176 fn convex_hull_colinear_rev() {
177 let points = vec![
178 Point::new([0, 0]),
179 Point::new([1, 0]),
180 Point::new([0, 9]),
181 Point::new([0, 8]),
182 Point::new([0, 7]),
183 Point::new([0, 6]),
184 ];
185 let poly = convex_hull(points).unwrap();
186 assert_ok!(poly.validate());
187 }
188
189 #[test]
190 fn convex_hull_dups() {
191 let points = vec![
192 Point::new([0, 0]),
193 Point::new([1, 0]),
194 Point::new([0, 0]),
195 Point::new([1, 0]),
196 Point::new([2, 2]),
197 Point::new([2, 2]),
198 Point::new([5, 1]),
199 Point::new([5, 1]),
200 ];
201 let poly = convex_hull(points).unwrap();
202 assert_ok!(poly.validate());
203 }
204
205 #[test]
206 fn convex_hull_insufficient_dups() {
207 let points = vec![
208 Point::new([0, 0]),
209 Point::new([0, 0]),
210 Point::new([2, 2]),
211 Point::new([2, 2]),
212 Point::new([0, 0]),
213 Point::new([2, 2]),
214 ];
215 assert_eq!(convex_hull(points).err(), Some(Error::InsufficientVertices));
216 }
217
218 #[test]
219 fn convex_hull_invalid() {
220 let points: Vec<Point<i64>> = vec![
221 Point { array: [0, 0] },
222 Point { array: [100, 0] },
223 Point { array: [50, 1] },
224 Point { array: [40, 1] },
225 Point { array: [0, 100] },
226 ];
227 let points: Vec<Point<BigInt, 2>> = points.into_iter().map(|pt| pt.cast()).collect();
228 let poly = convex_hull(points).unwrap();
229 assert_ok!(poly.validate());
230 }
231
232 #[test]
233 fn unit_1() {
234 let points: Vec<Point<BigInt>> = vec![
235 Point::new([0, 0]).into(),
236 Point::new([-1, 1]).into(),
237 Point::new([0, 1]).into(),
238 Point::new([-717193444810564826, 1]).into(),
239 ];
240 let poly = convex_hull(points).unwrap();
241 assert_ok!(poly.validate());
242 }
243
244 #[test]
245 fn unit_2() {
246 let points: Vec<Point<i8>> = vec![
247 Point::new([0, 0]),
248 Point::new([0, -10]),
249 Point::new([-13, 0]),
250 ];
251 let poly = convex_hull(points).unwrap();
252 assert_ok!(poly.validate());
253 }
254
255 #[proptest]
256 fn convex_hull_prop(#[strategy(vec(any_r(), 0..100))] pts: Vec<Point<BigInt>>) {
257 if let Ok(poly) = convex_hull(pts.clone()) {
258 prop_assert_eq!(poly.validate().err(), None);
260 for pt in pts.iter() {
262 prop_assert_ne!(poly.locate(pt), PointLocation::Outside)
263 }
264 for pt in poly.iter() {
266 prop_assert!(pts.contains(pt))
267 }
268 }
269 }
270
271 #[proptest]
272 fn convex_hull_prop_i8(#[strategy(vec(any::<Point<i8>>(), 0..100))] pts: Vec<Point<i8>>) {
273 if let Ok(poly) = convex_hull(pts.clone()) {
274 prop_assert_eq!(poly.validate().err(), None);
276 for pt in pts.iter() {
278 prop_assert_ne!(poly.locate(pt), PointLocation::Outside)
279 }
280 for pt in poly.iter() {
282 prop_assert!(pts.contains(pt))
283 }
284 }
285 }
286}