1use std::collections::HashMap;
2
3use geo_types::{Coord, Line, LineString, MultiPolygon, Polygon, Triangle};
4
5use crate::winding_order::{WindingOrder, triangle_winding_order};
6use crate::{Contains, GeoFloat};
7
8#[derive(Debug)]
11pub enum LineStitchingError {
12 IncompleteRing(&'static str),
13}
14
15impl std::fmt::Display for LineStitchingError {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 write!(f, "{self:?}")
18 }
19}
20
21impl std::error::Error for LineStitchingError {}
22
23pub(crate) type TriangleStitchingResult<T> = Result<T, LineStitchingError>;
24
25#[deprecated(
28 since = "0.32.1",
29 note = "Output is not always valid - use unary_union which is typically faster and produces valid output. Convert your triangles to polygon `triangle.to_polygon()` first."
30)]
31pub trait StitchTriangles<T: GeoFloat>: private::Stitchable<T> {
33 fn stitch_triangulation(&self) -> TriangleStitchingResult<MultiPolygon<T>>;
132}
133
134mod private {
135 use super::*;
136
137 pub trait Stitchable<T: GeoFloat>: AsRef<[Triangle<T>]> {}
138 impl<S, T> Stitchable<T> for S
139 where
140 S: AsRef<[Triangle<T>]>,
141 T: GeoFloat,
142 {
143 }
144}
145
146#[allow(deprecated)]
147impl<S, T> StitchTriangles<T> for S
148where
149 S: private::Stitchable<T>,
150 T: GeoFloat,
151{
152 fn stitch_triangulation(&self) -> TriangleStitchingResult<MultiPolygon<T>> {
153 stitch_triangles(self.as_ref().iter())
154 }
155}
156
157fn stitch_triangles<'a, T, S>(triangles: S) -> TriangleStitchingResult<MultiPolygon<T>>
159where
160 T: GeoFloat + 'a,
161 S: Iterator<Item = &'a Triangle<T>>,
162{
163 let lines = triangles.flat_map(ccw_lines).collect::<Vec<_>>();
164
165 let boundary_lines = find_boundary_lines(lines);
166 let stitched_multipolygon = stitch_multipolygon_from_lines(boundary_lines)?;
167
168 let polys = stitched_multipolygon
169 .into_iter()
170 .map(find_and_fix_holes_in_exterior)
171 .collect::<Vec<_>>();
172
173 Ok(MultiPolygon::new(polys))
174}
175
176fn ccw_lines<T: GeoFloat>(tri: &Triangle<T>) -> [Line<T>; 3] {
178 match triangle_winding_order(tri) {
179 Some(WindingOrder::CounterClockwise) => tri.to_lines(),
180 _ => {
181 let [a, b, c] = tri.to_array();
182 [(b, a), (a, c), (c, b)].map(|(start, end)| Line::new(start, end))
183 }
184 }
185}
186
187#[inline]
189fn same_line<T: GeoFloat>(l1: &Line<T>, l2: &Line<T>) -> bool {
190 (l1.start == l2.start && l1.end == l2.end) || (l1.start == l2.end && l2.start == l1.end)
191}
192
193fn find_boundary_lines<T: GeoFloat>(lines: Vec<Line<T>>) -> Vec<Line<T>> {
201 lines.into_iter().fold(Vec::new(), |mut lines, new_line| {
202 if let Some(idx) = lines.iter().position(|line| same_line(line, &new_line)) {
203 lines.remove(idx);
204 } else {
205 lines.push(new_line);
206 }
207 lines
208 })
209}
210
211fn find_and_fix_holes_in_exterior<F: GeoFloat>(mut poly: Polygon<F>) -> Polygon<F> {
217 fn detect_if_rings_closed_with_point<F: GeoFloat>(
218 points: &mut Vec<Coord<F>>,
219 p: Coord<F>,
220 ) -> Option<Vec<Coord<F>>> {
221 let pos = points.iter().position(|&c| c == p)?;
223
224 let ring = points
226 .drain(pos..)
227 .chain(std::iter::once(p))
228 .collect::<Vec<_>>();
229 Some(ring)
230 }
231
232 let rings = {
234 let (points, mut rings) =
235 poly.exterior()
236 .into_iter()
237 .fold((vec![], vec![]), |(mut points, mut rings), coord| {
238 rings.extend(detect_if_rings_closed_with_point(&mut points, *coord));
239 points.push(*coord);
240 (points, rings)
241 });
242
243 rings.push(points);
245
246 rings
247 };
248
249 let mut rings = rings
251 .into_iter()
252 .filter(|cs| cs.len() >= 3)
254 .map(|cs| Polygon::new(LineString::new(cs), vec![]))
255 .collect::<Vec<_>>();
256
257 fn find_outmost_ring<F: GeoFloat>(rings: &[Polygon<F>]) -> Option<usize> {
259 let enumerated_rings = || rings.iter().enumerate();
260 enumerated_rings()
261 .find(|(i, ring)| {
262 enumerated_rings()
263 .filter(|(j, _)| i != j)
264 .all(|(_, other)| ring.contains(other))
265 })
266 .map(|(i, _)| i)
267 }
268
269 if let Some(outer_index) = find_outmost_ring(&rings) {
275 let exterior = rings.remove(outer_index).exterior().clone();
276 let interiors = poly
277 .interiors()
278 .iter()
279 .cloned()
280 .chain(rings.into_iter().map(|p| p.exterior().clone()))
281 .collect::<Vec<_>>();
282 poly = Polygon::new(exterior, interiors);
283 }
284 poly
285}
286
287fn stitch_multipolygon_from_lines<F: GeoFloat>(
289 lines: Vec<Line<F>>,
290) -> TriangleStitchingResult<MultiPolygon<F>> {
291 let rings = stitch_rings_from_lines(lines)?;
292
293 fn find_parent_idxs<F: GeoFloat>(
294 ring_idx: usize,
295 ring: &LineString<F>,
296 all_rings: &[LineString<F>],
297 ) -> Vec<usize> {
298 all_rings
299 .iter()
300 .enumerate()
301 .filter(|(other_idx, _)| ring_idx != *other_idx)
302 .filter_map(|(idx, maybe_parent)| {
303 Polygon::new(maybe_parent.clone(), vec![])
304 .contains(ring)
305 .then_some(idx)
306 })
307 .collect()
308 }
309
310 let parents_of: HashMap<usize, Vec<usize>> = rings
312 .iter()
313 .enumerate()
314 .map(|(ring_idx, ring)| {
315 let parent_idxs = find_parent_idxs(ring_idx, ring, &rings);
316 (ring_idx, parent_idxs)
317 })
318 .collect();
319
320 let mut polygons_idxs: HashMap<usize, Vec<usize>> = HashMap::default();
322
323 fn find_direct_parent(
325 parent_rings: &[usize],
326 parents_of: &HashMap<usize, Vec<usize>>,
327 ) -> Option<usize> {
328 parent_rings
329 .iter()
330 .filter_map(|ring_idx| {
331 parents_of
332 .get(ring_idx)
333 .map(|grandparent_rings| (ring_idx, grandparent_rings))
334 })
335 .max_by_key(|(_, grandparent_rings)| grandparent_rings.len())
336 .map(|(idx, _)| idx)
337 .copied()
338 }
339
340 for (ring_index, parent_idxs) in parents_of.iter() {
345 let parent_count = parent_idxs.len();
346
347 if parent_count % 2 == 0 {
350 polygons_idxs.entry(*ring_index).or_default();
351 continue;
352 }
353
354 let maybe_direct_parent = find_direct_parent(parent_idxs, &parents_of);
358
359 debug_assert!(
364 maybe_direct_parent.is_some(),
365 "A direct parent has to exist"
366 );
367
368 if let Some(direct_parent) = maybe_direct_parent {
370 polygons_idxs
371 .entry(direct_parent)
372 .or_default()
373 .push(*ring_index);
374 }
375 }
376
377 let polygons = polygons_idxs
379 .into_iter()
380 .map(|(parent_idx, children_idxs)| {
381 let exterior = rings[parent_idx].clone();
383 let interiors = children_idxs
384 .into_iter()
385 .map(|child_idx| rings[child_idx].clone())
386 .collect::<Vec<_>>();
387 (exterior, interiors)
388 })
389 .map(|(exterior, interiors)| Polygon::new(exterior, interiors));
390
391 Ok(polygons.collect())
392}
393
394fn stitch_rings_from_lines<F: GeoFloat>(
397 lines: Vec<Line<F>>,
398) -> TriangleStitchingResult<Vec<LineString<F>>> {
399 let mut ring_parts: Vec<Vec<Coord<F>>> = lines
401 .iter()
402 .map(|line| vec![line.start, line.end])
403 .collect();
404
405 let mut rings: Vec<LineString<F>> = vec![];
406 while let Some(last_part) = ring_parts.pop() {
409 let (j, compound_part) = ring_parts
410 .iter()
411 .enumerate()
412 .find_map(|(j, other_part)| {
413 let new_part = try_stitch(&last_part, other_part)?;
414 Some((j, new_part))
415 })
416 .ok_or(LineStitchingError::IncompleteRing("Couldn't reconstruct polygons from the inputs. Please check them for invalidities."))?;
417 ring_parts.remove(j);
418
419 let is_ring = compound_part.first() == compound_part.last() && !compound_part.is_empty();
420
421 if is_ring {
422 let new_ring = LineString::new(compound_part);
423 rings.push(new_ring);
424 } else {
425 ring_parts.push(compound_part);
426 }
427 }
428
429 Ok(rings)
430}
431
432fn try_stitch<F: GeoFloat>(a: &[Coord<F>], b: &[Coord<F>]) -> Option<Vec<Coord<F>>> {
433 let a_first = a.first()?;
434 let a_last = a.last()?;
435 let b_first = b.first()?;
436 let b_last = b.last()?;
437
438 let a = || a.iter();
439 let b = || b.iter();
440
441 (a_last == b_first)
443 .then(|| a().chain(b().skip(1)).cloned().collect())
444 .or_else(|| (a_first == b_last).then(|| b().chain(a().skip(1)).cloned().collect()))
446}
447
448#[allow(deprecated)]
451#[cfg(test)]
452mod polygon_stitching_tests {
453
454 use crate::{Relate, TriangulateEarcut, Validation, Winding};
455
456 use super::*;
457 use geo_types::*;
458
459 #[test]
460 fn poly_inside_a_donut() {
461 _ = pretty_env_logger::try_init();
462 let zero = Coord::zero();
463 let one = Point::new(1.0, 1.0).0;
464 let outer_outer = Rect::new(zero, one * 5.0);
465 let inner_outer = Rect::new(one, one * 4.0);
466 let outer = Polygon::new(
467 outer_outer.to_polygon().exterior().clone(),
468 vec![inner_outer.to_polygon().exterior().clone()],
469 );
470 let inner = Rect::new(one * 2.0, one * 3.0).to_polygon();
471
472 let mp = MultiPolygon::new(vec![outer.clone(), inner.clone()]);
473
474 let tris = [inner, outer].map(|p| p.earcut_triangles()).concat();
475
476 let result = tris.stitch_triangulation().unwrap();
477
478 assert!(mp.relate(&result).is_equal_topo());
479 }
480
481 #[test]
482 fn stitch_independent_of_orientation() {
483 _ = pretty_env_logger::try_init();
484 let mut tri1 = Triangle::from([
485 Coord { x: 0.0, y: 0.0 },
486 Coord { x: 1.0, y: 0.0 },
487 Coord { x: 0.0, y: 1.0 },
488 ])
489 .to_polygon();
490 let mut tri2 = Triangle::from([
491 Coord { x: 1.0, y: 1.0 },
492 Coord { x: 1.0, y: 0.0 },
493 Coord { x: 0.0, y: 1.0 },
494 ])
495 .to_polygon();
496
497 tri1.exterior_mut(|ls| ls.make_ccw_winding());
498 tri2.exterior_mut(|ls| ls.make_ccw_winding());
499 let result_1 = [tri1.clone(), tri2.clone()]
500 .map(|tri| tri.earcut_triangles())
501 .concat()
502 .stitch_triangulation()
503 .unwrap();
504
505 tri1.exterior_mut(|ls| ls.make_cw_winding());
506 tri2.exterior_mut(|ls| ls.make_ccw_winding());
507 let result_2 = [tri1.clone(), tri2.clone()]
508 .map(|tri| tri.earcut_triangles())
509 .concat()
510 .stitch_triangulation()
511 .unwrap();
512
513 tri1.exterior_mut(|ls| ls.make_cw_winding());
514 tri2.exterior_mut(|ls| ls.make_cw_winding());
515 let result_3 = [tri1.clone(), tri2.clone()]
516 .map(|tri| tri.earcut_triangles())
517 .concat()
518 .stitch_triangulation()
519 .unwrap();
520
521 tri1.exterior_mut(|ls| ls.make_ccw_winding());
522 tri2.exterior_mut(|ls| ls.make_cw_winding());
523 let result_4 = [tri1, tri2]
524 .map(|tri| tri.earcut_triangles())
525 .concat()
526 .stitch_triangulation()
527 .unwrap();
528
529 assert!(result_1.relate(&result_2).is_equal_topo());
530 assert!(result_2.relate(&result_3).is_equal_topo());
531 assert!(result_3.relate(&result_4).is_equal_topo());
532 }
533
534 #[test]
535 fn stitch_creating_hole() {
536 let poly1 = wkt!(POLYGON((0.0 0.0,1.0 0.0,1.0 1.0,1.0 2.0,2.0 2.0,2.0 1.0,2.0 0.0,3.0 0.0,3.0 3.0,0.0 3.0,0.0 0.0)));
537 let poly2 = wkt!(POLYGON((1.0 0.0,2.0 0.0,2.0 1.0,1.0 1.0,1.0 0.0)));
538
539 let result = [poly1, poly2]
540 .map(|p| p.earcut_triangles())
541 .concat()
542 .stitch_triangulation()
543 .unwrap();
544
545 assert_eq!(result.0.len(), 1);
546 assert_eq!(result[0].interiors().len(), 1);
547 }
548
549 #[test]
550 fn inner_banana_produces_hole() {
551 let poly = wkt!(POLYGON((0.0 0.0,4.0 0.0,3.0 2.0,5.0 2.0,4.0 0.0,8.0 0.0,4.0 4.0,0.0 0.0)));
552
553 let result = [poly]
554 .map(|p| p.earcut_triangles())
555 .concat()
556 .stitch_triangulation()
557 .unwrap();
558
559 assert_eq!(result.0.len(), 1);
560 assert_eq!(result[0].interiors().len(), 1);
561 }
562
563 #[test]
564 #[should_panic(expected = "should have 2 separate polygons")]
565 fn outer_banana_doesnt_produce_hole() {
566 let poly =
567 wkt!(POLYGON((0.0 0.0,4.0 0.0,3.0 -2.0,5.0 -2.0,4.0 0.0,8.0 0.0,4.0 4.0,0.0 0.0)));
568
569 let result = [poly]
570 .map(|p| p.earcut_triangles())
571 .concat()
572 .stitch_triangulation()
573 .unwrap();
574
575 assert_eq!(result.0.len(), 2, "should have 2 separate polygons");
578 result[0].check_validation().unwrap();
579
580 assert_eq!(result[0].interiors().len(), 0);
581 }
582
583 #[test]
594 fn document_bug_in_stitch_rings_order_dependent_at_non_manifold_vertices() {
595 let old_earcutr_order = vec![
597 wkt!(TRIANGLE(4.0 0.0,4.0 4.0,0.0 0.0)), wkt!(TRIANGLE(4.0 0.0,3.0 -2.0,5.0 -2.0)), wkt!(TRIANGLE(8.0 0.0,4.0 4.0,4.0 0.0)), ];
601 let old_result = old_earcutr_order.stitch_triangulation().unwrap();
602 assert_eq!(old_result.0.len(), 2); old_result.check_validation().unwrap();
604
605 let new_earcut_order = vec![
609 wkt!(TRIANGLE(4.0 0.0,4.0 4.0,0.0 0.0)), wkt!(TRIANGLE(4.0 0.0,8.0 0.0,4.0 4.0)), wkt!(TRIANGLE(4.0 0.0,3.0 -2.0,5.0 -2.0)), ];
613 let poly =
614 wkt!(POLYGON((0.0 0.0,4.0 0.0,3.0 -2.0,5.0 -2.0,4.0 0.0,8.0 0.0,4.0 4.0,0.0 0.0)));
615 assert_eq!(poly.earcut_triangles(), new_earcut_order);
616
617 let new_result = new_earcut_order.stitch_triangulation().unwrap();
618 assert_eq!(new_result.0.len(), 1); new_result.check_validation().unwrap_err();
620 }
621}