1use crate::{
2 Contains, ConvexHull, Coord, CoordNum, GeoFloat, Intersects, LineString, MultiPoint, Point,
3 Polygon,
4};
5use num_traits::Float;
6use rstar::RTreeNum;
7use std::cmp::max;
8
9const K_MULTIPLIER: f32 = 1.5;
10
11pub trait KNearestConcaveHull {
37 type Scalar: CoordNum;
38 fn k_nearest_concave_hull(&self, k: u32) -> Polygon<Self::Scalar>;
39}
40
41impl<T> KNearestConcaveHull for Vec<Point<T>>
42where
43 T: GeoFloat + RTreeNum,
44{
45 type Scalar = T;
46 fn k_nearest_concave_hull(&self, k: u32) -> Polygon<Self::Scalar> {
47 concave_hull(self.iter().map(|point| &point.0), k)
48 }
49}
50
51impl<T> KNearestConcaveHull for [Point<T>]
52where
53 T: GeoFloat + RTreeNum,
54{
55 type Scalar = T;
56 fn k_nearest_concave_hull(&self, k: u32) -> Polygon<Self::Scalar> {
57 concave_hull(self.iter().map(|point| &point.0), k)
58 }
59}
60
61impl<T> KNearestConcaveHull for Vec<Coord<T>>
62where
63 T: GeoFloat + RTreeNum,
64{
65 type Scalar = T;
66 fn k_nearest_concave_hull(&self, k: u32) -> Polygon<Self::Scalar> {
67 concave_hull(self.iter(), k)
68 }
69}
70
71impl<T> KNearestConcaveHull for [Coord<T>]
72where
73 T: GeoFloat + RTreeNum,
74{
75 type Scalar = T;
76 fn k_nearest_concave_hull(&self, k: u32) -> Polygon<Self::Scalar> {
77 concave_hull(self.iter(), k)
78 }
79}
80
81impl<T> KNearestConcaveHull for MultiPoint<T>
82where
83 T: GeoFloat + RTreeNum,
84{
85 type Scalar = T;
86 fn k_nearest_concave_hull(&self, k: u32) -> Polygon<Self::Scalar> {
87 concave_hull(self.iter().map(|point| &point.0), k)
88 }
89}
90
91fn concave_hull<'a, T: 'a>(coords: impl Iterator<Item = &'a Coord<T>>, k: u32) -> Polygon<T>
92where
93 T: GeoFloat + RTreeNum,
94{
95 let dataset = prepare_dataset(coords);
96 concave_hull_inner(dataset, k)
97}
98
99const DELTA: f32 = 0.000000001;
100
101fn prepare_dataset<'a, T: 'a>(coords: impl Iterator<Item = &'a Coord<T>>) -> rstar::RTree<Coord<T>>
103where
104 T: GeoFloat + RTreeNum,
105{
106 let mut dataset: rstar::RTree<Coord<T>> = rstar::RTree::new();
107 for coord in coords {
108 let closest = dataset.nearest_neighbor(coord);
109 if let Some(closest) = closest {
110 if coords_are_equal(coord, closest) {
111 continue;
112 }
113 }
114
115 dataset.insert(*coord)
116 }
117
118 dataset
119}
120
121fn coords_are_equal<T>(c1: &Coord<T>, c2: &Coord<T>) -> bool
124where
125 T: GeoFloat + RTreeNum,
126{
127 float_equal(c1.x, c2.x) && float_equal(c1.y, c2.y)
128}
129
130fn float_equal<T>(a: T, b: T) -> bool
131where
132 T: GeoFloat,
133{
134 let da = a * T::from(DELTA)
135 .expect("Conversion from constant is always valid.")
136 .abs();
137 b > (a - da) && b < (a + da)
138}
139
140fn polygon_from_tree<T>(dataset: &rstar::RTree<Coord<T>>) -> Polygon<T>
141where
142 T: GeoFloat + RTreeNum,
143{
144 assert!(dataset.size() <= 3);
145
146 let mut coords: Vec<Coord<T>> = dataset.iter().cloned().collect();
147 if !coords.is_empty() {
148 coords.push(coords[0]);
150 }
151
152 Polygon::new(LineString::from(coords), vec![])
153}
154
155fn concave_hull_inner<T>(original_dataset: rstar::RTree<Coord<T>>, k: u32) -> Polygon<T>
156where
157 T: GeoFloat + RTreeNum,
158{
159 let set_length = original_dataset.size();
160 if set_length <= 3 {
161 return polygon_from_tree(&original_dataset);
162 }
163 if k >= set_length as u32 {
164 return fall_back_hull(&original_dataset);
165 }
166
167 let k_adjusted = adjust_k(k);
168 let mut dataset = original_dataset.clone();
169
170 let first_coord = get_first_coord(&dataset);
171 let mut hull = vec![first_coord];
172
173 let mut current_coord = first_coord;
174 dataset.remove(&first_coord);
175
176 let mut prev_coord = current_coord;
177 let mut curr_step = 2;
178 while (current_coord != first_coord || curr_step == 2) && dataset.size() > 0 {
179 if curr_step == 5 {
180 dataset.insert(first_coord);
181 }
182
183 let mut nearest_coords: Vec<_> =
184 get_nearest_coords(&dataset, ¤t_coord, k_adjusted).collect();
185 sort_by_angle(&mut nearest_coords, ¤t_coord, &prev_coord);
186
187 let selected = nearest_coords
188 .iter()
189 .find(|x| !intersects(&hull, &[¤t_coord, x]));
190
191 if let Some(sel) = selected {
192 prev_coord = current_coord;
193 current_coord = **sel;
194 hull.push(current_coord);
195 dataset.remove(¤t_coord);
196
197 curr_step += 1;
198 } else {
199 return concave_hull_inner(original_dataset, get_next_k(k_adjusted));
200 }
201 }
202
203 let poly = Polygon::new(LineString::from(hull), vec![]);
204
205 if original_dataset
206 .iter()
207 .any(|&coord| !coord_inside(&coord, &poly))
208 {
209 return concave_hull_inner(original_dataset, get_next_k(k_adjusted));
210 }
211
212 poly
213}
214
215fn fall_back_hull<T>(dataset: &rstar::RTree<Coord<T>>) -> Polygon<T>
216where
217 T: GeoFloat + RTreeNum,
218{
219 let multipoint = MultiPoint::from(dataset.iter().cloned().collect::<Vec<Coord<T>>>());
220 multipoint.convex_hull()
221}
222
223fn get_next_k(curr_k: u32) -> u32 {
224 max(curr_k + 1, ((curr_k as f32) * K_MULTIPLIER) as u32)
225}
226
227fn adjust_k(k: u32) -> u32 {
228 max(k, 3)
229}
230
231fn get_first_coord<T>(coord_set: &rstar::RTree<Coord<T>>) -> Coord<T>
232where
233 T: GeoFloat + RTreeNum,
234{
235 let mut min_y = Float::max_value();
236 let mut result = coord_set
237 .iter()
238 .next()
239 .expect("We checked that there are more then 3 coords in the set before.");
240
241 for coord in coord_set.iter() {
242 if coord.y < min_y {
243 min_y = coord.y;
244 result = coord;
245 }
246 }
247
248 *result
249}
250
251fn get_nearest_coords<'a, T>(
252 dataset: &'a rstar::RTree<Coord<T>>,
253 base_coord: &Coord<T>,
254 candidate_no: u32,
255) -> impl Iterator<Item = &'a Coord<T>>
256where
257 T: GeoFloat + RTreeNum,
258{
259 dataset
260 .nearest_neighbor_iter(base_coord)
261 .take(candidate_no as usize)
262}
263
264fn sort_by_angle<T>(coords: &mut [&Coord<T>], curr_coord: &Coord<T>, prev_coord: &Coord<T>)
265where
266 T: GeoFloat,
267{
268 let base_angle = pseudo_angle(prev_coord.x - curr_coord.x, prev_coord.y - curr_coord.y);
269 coords.sort_by(|a, b| {
270 let mut angle_a = pseudo_angle(a.x - curr_coord.x, a.y - curr_coord.y) - base_angle;
271 if angle_a < T::zero() {
272 angle_a = angle_a + T::from(4.0).unwrap();
273 }
274
275 let mut angle_b = pseudo_angle(b.x - curr_coord.x, b.y - curr_coord.y) - base_angle;
276 if angle_b < T::zero() {
277 angle_b = angle_b + T::from(4.0).unwrap();
278 }
279
280 angle_a.partial_cmp(&angle_b).unwrap().reverse()
281 });
282}
283
284fn pseudo_angle<T>(dx: T, dy: T) -> T
285where
286 T: GeoFloat,
287{
288 if dx == T::zero() && dy == T::zero() {
289 return T::zero();
290 }
291
292 let p = dx / (dx.abs() + dy.abs());
293 if dy < T::zero() {
294 T::from(3.).unwrap() + p
295 } else {
296 T::from(1.).unwrap() - p
297 }
298}
299
300fn intersects<T>(hull: &[Coord<T>], line: &[&Coord<T>; 2]) -> bool
301where
302 T: GeoFloat,
303{
304 if *line[1] == hull[0] {
306 return false;
307 }
308
309 let coords = hull.iter().take(hull.len() - 1).cloned().collect();
310 let linestring = LineString::new(coords);
311 let line = crate::Line::new(*line[0], *line[1]);
312 linestring.intersects(&line)
313}
314
315fn coord_inside<T>(coord: &Coord<T>, poly: &Polygon<T>) -> bool
316where
317 T: GeoFloat,
318{
319 poly.contains(coord) || poly.exterior().contains(coord)
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325 use crate::coords_iter::CoordsIter;
326 use crate::geo_types::coord;
327
328 #[test]
329 fn coord_ordering() {
330 let coords = vec![
331 coord!(x: 1.0, y: 1.0),
332 coord!(x: -1.0, y: 0.0),
333 coord!(x: 0.0, y: 1.0),
334 coord!(x: 1.0, y: 0.0),
335 ];
336
337 let mut coords_mapped: Vec<&Coord<f32>> = coords.iter().collect();
338
339 let center = coord!(x: 0.0, y: 0.0);
340 let prev_coord = coord!(x: 1.0, y: 1.0);
341
342 let expected = vec![&coords[3], &coords[1], &coords[2], &coords[0]];
343
344 sort_by_angle(&mut coords_mapped, ¢er, &prev_coord);
345 assert_eq!(coords_mapped, expected);
346
347 let expected = vec![&coords[1], &coords[2], &coords[0], &coords[3]];
348
349 let prev_coord = coord!(x: 1.0, y: -1.0);
350 sort_by_angle(&mut coords_mapped, ¢er, &prev_coord);
351 assert_eq!(coords_mapped, expected);
352 }
353
354 #[test]
355 fn get_first_coord_test() {
356 let coords = vec![
357 coord!(x: 1.0, y: 1.0),
358 coord!(x: -1.0, y: 0.0),
359 coord!(x: 0.0, y: 1.0),
360 coord!(x: 0.0, y: 0.5),
361 ];
362 let tree = rstar::RTree::bulk_load(coords);
363 let first = coord!(x: -1.0, y: 0.0);
364
365 assert_eq!(get_first_coord(&tree), first);
366 }
367
368 #[test]
369 fn concave_hull_test() {
370 let coords = vec![
371 coord!(x: 0.0, y: 0.0),
372 coord!(x: 1.0, y: 0.0),
373 coord!(x: 2.0, y: 0.0),
374 coord!(x: 3.0, y: 0.0),
375 coord!(x: 0.0, y: 1.0),
376 coord!(x: 1.0, y: 1.0),
377 coord!(x: 2.0, y: 1.0),
378 coord!(x: 3.0, y: 1.0),
379 coord!(x: 0.0, y: 2.0),
380 coord!(x: 1.0, y: 2.5),
381 coord!(x: 2.0, y: 2.5),
382 coord!(x: 3.0, y: 2.0),
383 coord!(x: 0.0, y: 3.0),
384 coord!(x: 3.0, y: 3.0),
385 ];
386
387 let poly = concave_hull(coords.iter(), 3);
388 assert_eq!(poly.exterior().coords_count(), 12);
389
390 let must_not_be_in = vec![&coords[6]];
391 for coord in poly.exterior().coords_iter() {
392 for not_coord in must_not_be_in.iter() {
393 assert_ne!(&coord, *not_coord);
394 }
395 }
396 }
397
398 #[test]
399 fn empty_hull() {
400 let actual: Polygon<f64> = concave_hull(vec![].iter(), 3);
401 let expected = Polygon::new(LineString::new(vec![]), vec![]);
402 assert_eq!(actual, expected);
403 }
404}