1use crate::convex_hull::qhull;
2use crate::utils::partial_min;
3use crate::{
4 coord, Centroid, Coord, CoordNum, EuclideanDistance, EuclideanLength, GeoFloat, Line,
5 LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
6};
7use rstar::{RTree, RTreeNum};
8use std::collections::VecDeque;
9
10pub trait ConcaveHull {
45 type Scalar: CoordNum;
46 fn concave_hull(&self, concavity: Self::Scalar) -> Polygon<Self::Scalar>;
47}
48
49impl<T> ConcaveHull for Polygon<T>
50where
51 T: GeoFloat + RTreeNum,
52{
53 type Scalar = T;
54 fn concave_hull(&self, concavity: Self::Scalar) -> Polygon<Self::Scalar> {
55 let mut points: Vec<_> = self.exterior().0.clone();
56 Polygon::new(concave_hull(&mut points, concavity), vec![])
57 }
58}
59
60impl<T> ConcaveHull for MultiPolygon<T>
61where
62 T: GeoFloat + RTreeNum,
63{
64 type Scalar = T;
65 fn concave_hull(&self, concavity: Self::Scalar) -> Polygon<Self::Scalar> {
66 let mut aggregated: Vec<Coord<Self::Scalar>> = self
67 .0
68 .iter()
69 .flat_map(|elem| elem.exterior().0.clone())
70 .collect();
71 Polygon::new(concave_hull(&mut aggregated, concavity), vec![])
72 }
73}
74
75impl<T> ConcaveHull for LineString<T>
76where
77 T: GeoFloat + RTreeNum,
78{
79 type Scalar = T;
80 fn concave_hull(&self, concavity: Self::Scalar) -> Polygon<Self::Scalar> {
81 Polygon::new(concave_hull(&mut self.0.clone(), concavity), vec![])
82 }
83}
84
85impl<T> ConcaveHull for MultiLineString<T>
86where
87 T: GeoFloat + RTreeNum,
88{
89 type Scalar = T;
90 fn concave_hull(&self, concavity: T) -> Polygon<T> {
91 let mut aggregated: Vec<Coord<T>> = self.iter().flat_map(|elem| elem.0.clone()).collect();
92 Polygon::new(concave_hull(&mut aggregated, concavity), vec![])
93 }
94}
95
96impl<T> ConcaveHull for MultiPoint<T>
97where
98 T: GeoFloat + RTreeNum,
99{
100 type Scalar = T;
101 fn concave_hull(&self, concavity: T) -> Polygon<T> {
102 let mut coordinates: Vec<Coord<T>> = self.iter().map(|point| point.0).collect();
103 Polygon::new(concave_hull(&mut coordinates, concavity), vec![])
104 }
105}
106
107fn find_point_closest_to_line<T>(
108 interior_coords_tree: &RTree<Coord<T>>,
109 line: Line<T>,
110 max_dist: T,
111 edge_length: T,
112 concavity: T,
113 line_tree: &RTree<Line<T>>,
114) -> Option<Coord<T>>
115where
116 T: GeoFloat + RTreeNum,
117{
118 let h = max_dist + max_dist;
119 let w = line.euclidean_length() + h;
120 let two = T::add(T::one(), T::one());
121 let search_dist = T::div(T::sqrt(T::powi(w, 2) + T::powi(h, 2)), two);
122 let centroid = line.centroid();
123 let centroid_coord = coord! {
124 x: centroid.x(),
125 y: centroid.y(),
126 };
127 let mut candidates = interior_coords_tree
128 .locate_within_distance(centroid_coord, search_dist)
129 .peekable();
130 let peek = candidates.peek();
131 match peek {
132 None => None,
133 Some(&point) => {
134 let closest_point =
135 candidates.fold(Point::new(point.x, point.y), |acc_point, candidate| {
136 let candidate_point = Point::new(candidate.x, candidate.y);
137 if line.euclidean_distance(&acc_point)
138 > line.euclidean_distance(&candidate_point)
139 {
140 candidate_point
141 } else {
142 acc_point
143 }
144 });
145 let mut edges_nearby_point = line_tree
146 .locate_within_distance(closest_point, search_dist)
147 .peekable();
148 let peeked_edge = edges_nearby_point.peek();
149
150 #[allow(clippy::manual_map)]
154 let closest_edge_option = match peeked_edge {
155 None => None,
156 Some(&edge) => Some(edges_nearby_point.fold(*edge, |acc, candidate| {
157 if closest_point.euclidean_distance(&acc)
158 > closest_point.euclidean_distance(candidate)
159 {
160 *candidate
161 } else {
162 acc
163 }
164 })),
165 };
166 let decision_distance = partial_min(
167 closest_point.euclidean_distance(&line.start_point()),
168 closest_point.euclidean_distance(&line.end_point()),
169 );
170 if let Some(closest_edge) = closest_edge_option {
171 let far_enough = edge_length / decision_distance > concavity;
172 let are_edges_equal = closest_edge == line;
173 if far_enough && are_edges_equal {
174 Some(coord! {
175 x: closest_point.x(),
176 y: closest_point.y(),
177 })
178 } else {
179 None
180 }
181 } else {
182 None
183 }
184 }
185 }
186}
187
188fn concave_hull<T>(coords: &mut [Coord<T>], concavity: T) -> LineString<T>
191where
192 T: GeoFloat + RTreeNum,
193{
194 let hull = qhull::quick_hull(coords);
195
196 if coords.len() < 4 {
197 return hull;
198 }
199
200 let hull_tree: RTree<Coord<T>> = RTree::bulk_load(hull.clone().0);
202
203 let interior_coords: Vec<Coord<T>> = coords
204 .iter()
205 .filter(|coord| !hull_tree.contains(coord))
206 .copied()
207 .collect();
208 let mut interior_points_tree: RTree<Coord<T>> = RTree::bulk_load(interior_coords);
209 let mut line_tree: RTree<Line<T>> = RTree::new();
210
211 let mut concave_list: Vec<Point<T>> = vec![];
212 let lines = hull.lines();
213 let mut line_queue: VecDeque<Line<T>> = VecDeque::new();
214
215 for line in lines {
216 line_queue.push_back(line);
217 line_tree.insert(line);
218 }
219 while let Some(line) = line_queue.pop_front() {
220 let edge_length = line.euclidean_length();
221 let dist = edge_length / concavity;
222 let possible_closest_point = find_point_closest_to_line(
223 &interior_points_tree,
224 line,
225 dist,
226 edge_length,
227 concavity,
228 &line_tree,
229 );
230
231 if let Some(closest_point) = possible_closest_point {
232 interior_points_tree.remove(&closest_point);
233 line_tree.remove(&line);
234 let point = Point::new(closest_point.x, closest_point.y);
235 let start_line = Line::new(line.start_point(), point);
236 let end_line = Line::new(point, line.end_point());
237 line_tree.insert(start_line);
238 line_tree.insert(end_line);
239 line_queue.push_front(end_line);
240 line_queue.push_front(start_line);
241 } else {
242 if concave_list.is_empty() || !concave_list.ends_with(&[line.start_point()]) {
244 concave_list.push(line.start_point());
245 }
246 concave_list.push(line.end_point());
247 }
248 }
249
250 concave_list.into()
251}
252
253#[cfg(test)]
254mod test {
255 use super::*;
256 use crate::{line_string, polygon};
257 use geo_types::Coord;
258
259 #[test]
260 fn triangle_test() {
261 let mut triangle = vec![
262 coord! { x: 0.0, y: 0.0 },
263 coord! { x: 4.0, y: 0.0 },
264 coord! { x: 2.0, y: 2.0 },
265 ];
266
267 let correct = line_string![
268 (x: 0.0, y: 0.0),
269 (x: 4.0, y: 0.0),
270 (x: 2.0, y: 2.0),
271 (x: 0.0, y: 0.0),
272 ];
273
274 let concavity = 2.0;
275 let res = concave_hull(&mut triangle, concavity);
276 assert_eq!(res, correct);
277 }
278
279 #[test]
280 fn square_test() {
281 let mut square = vec![
282 coord! { x: 0.0, y: 0.0 },
283 coord! { x: 4.0, y: 0.0 },
284 coord! { x: 4.0, y: 4.0 },
285 coord! { x: 0.0, y: 4.0 },
286 ];
287
288 let correct = line_string![
289 (x: 4.0, y: 0.0),
290 (x: 4.0, y: 4.0),
291 (x: 0.0, y: 4.0),
292 (x: 0.0, y: 0.0),
293 (x: 4.0, y: 0.0),
294 ];
295
296 let concavity = 2.0;
297 let res = concave_hull(&mut square, concavity);
298 assert_eq!(res, correct);
299 }
300
301 #[test]
302 fn one_flex_test() {
303 let mut v = vec![
304 coord! { x: 0.0, y: 0.0 },
305 coord! { x: 2.0, y: 1.0 },
306 coord! { x: 4.0, y: 0.0 },
307 coord! { x: 4.0, y: 4.0 },
308 coord! { x: 0.0, y: 4.0 },
309 ];
310 let correct = line_string![
311 (x: 4.0, y: 0.0),
312 (x: 4.0, y: 4.0),
313 (x: 0.0, y: 4.0),
314 (x: 0.0, y: 0.0),
315 (x: 2.0, y: 1.0),
316 (x: 4.0, y: 0.0),
317 ];
318 let concavity = 1.0;
319 let res = concave_hull(&mut v, concavity);
320 assert_eq!(res, correct);
321 }
322
323 #[test]
324 fn four_flex_test() {
325 let mut v = vec![
326 coord! { x: 0.0, y: 0.0 },
327 coord! { x: 2.0, y: 1.0 },
328 coord! { x: 4.0, y: 0.0 },
329 coord! { x: 3.0, y: 2.0 },
330 coord! { x: 4.0, y: 4.0 },
331 coord! { x: 2.0, y: 3.0 },
332 coord! { x: 0.0, y: 4.0 },
333 coord! { x: 1.0, y: 2.0 },
334 ];
335 let correct = line_string![
336 (x: 4.0, y: 0.0),
337 (x: 3.0, y: 2.0),
338 (x: 4.0, y: 4.0),
339 (x: 2.0, y: 3.0),
340 (x: 0.0, y: 4.0),
341 (x: 1.0, y: 2.0),
342 (x: 0.0, y: 0.0),
343 (x: 2.0, y: 1.0),
344 (x: 4.0, y: 0.0),
345 ];
346 let concavity = 1.7;
347 let res = concave_hull(&mut v, concavity);
348 assert_eq!(res, correct);
349 }
350
351 #[test]
352 fn consecutive_flex_test() {
353 let mut v = vec![
354 coord! { x: 0.0, y: 0.0 },
355 coord! { x: 4.0, y: 0.0 },
356 coord! { x: 4.0, y: 4.0 },
357 coord! { x: 3.0, y: 1.0 },
358 coord! { x: 3.0, y: 2.0 },
359 ];
360 let correct = line_string![
361 (x: 4.0, y: 0.0),
362 (x: 4.0, y: 4.0),
363 (x: 3.0, y: 2.0),
364 (x: 3.0, y: 1.0),
365 (x: 0.0, y: 0.0),
366 (x: 4.0, y: 0.0),
367 ];
368 let concavity = 2.0;
369 let res = concave_hull(&mut v, concavity);
370 assert_eq!(res, correct);
371 }
372
373 #[test]
374 fn concave_hull_norway_test() {
375 let norway = geo_test_fixtures::norway_main::<f64>();
376 let norway_concave_hull: LineString = geo_test_fixtures::norway_concave_hull::<f64>();
377 let res = norway.concave_hull(2.0);
378 assert_eq!(res.exterior(), &norway_concave_hull);
379 }
380
381 #[test]
382 fn concave_hull_linestring_test() {
383 let linestring = line_string![
384 (x: 0.0, y: 0.0),
385 (x: 4.0, y: 0.0),
386 (x: 4.0, y: 4.0),
387 (x: 3.0, y: 1.0),
388 (x: 3.0, y: 2.0)
389 ];
390 let concave = linestring.concave_hull(2.0);
391 let correct = vec![
392 Coord::from((4.0, 0.0)),
393 Coord::from((4.0, 4.0)),
394 Coord::from((3.0, 2.0)),
395 Coord::from((3.0, 1.0)),
396 Coord::from((0.0, 0.0)),
397 Coord::from((4.0, 0.0)),
398 ];
399 assert_eq!(concave.exterior().0, correct);
400 }
401
402 #[test]
403 fn concave_hull_multilinestring_test() {
404 let v1 = line_string![
405 (x: 0.0, y: 0.0),
406 (x: 4.0, y: 0.0)
407 ];
408 let v2 = line_string![
409 (x: 4.0, y: 4.0),
410 (x: 3.0, y: 1.0),
411 (x: 3.0, y: 2.0)
412 ];
413 let mls = MultiLineString::new(vec![v1, v2]);
414 let correct = vec![
415 Coord::from((4.0, 0.0)),
416 Coord::from((4.0, 4.0)),
417 Coord::from((3.0, 2.0)),
418 Coord::from((3.0, 1.0)),
419 Coord::from((0.0, 0.0)),
420 Coord::from((4.0, 0.0)),
421 ];
422 let res = mls.concave_hull(2.0);
423 assert_eq!(res.exterior().0, correct);
424 }
425
426 #[test]
427 fn concave_hull_multipolygon_test() {
428 let v1 = polygon![
429 (x: 0.0, y: 0.0),
430 (x: 4.0, y: 0.0)
431 ];
432 let v2 = polygon![
433 (x: 4.0, y: 4.0),
434 (x: 3.0, y: 1.0),
435 (x: 3.0, y: 2.0)
436 ];
437 let multipolygon = MultiPolygon::new(vec![v1, v2]);
438 let res = multipolygon.concave_hull(2.0);
439 let correct = vec![
440 Coord::from((4.0, 0.0)),
441 Coord::from((4.0, 4.0)),
442 Coord::from((3.0, 2.0)),
443 Coord::from((3.0, 1.0)),
444 Coord::from((0.0, 0.0)),
445 Coord::from((4.0, 0.0)),
446 ];
447 assert_eq!(res.exterior().0, correct);
448 }
449}