1use crate::error::{AlgorithmError, Result};
7use oxigdal_core::vector::{Coordinate, LineString, Point, Polygon};
8
9#[derive(Debug, Clone)]
11pub struct SplitOptions {
12 pub tolerance: f64,
14 pub snap_to_grid: bool,
16 pub grid_size: f64,
18 pub min_segment_length: f64,
20 pub preserve_all: bool,
22 pub min_area: Option<f64>,
24 pub preserve_orientation: bool,
26 pub keep_holes: bool,
28}
29
30impl Default for SplitOptions {
31 fn default() -> Self {
32 Self {
33 tolerance: 1e-10,
34 snap_to_grid: false,
35 grid_size: 1e-6,
36 min_segment_length: 0.0,
37 preserve_all: true,
38 min_area: None,
39 preserve_orientation: false,
40 keep_holes: true,
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct SplitResult {
48 pub geometries: Vec<SplitGeometry>,
50 pub num_splits: usize,
52 pub complete: bool,
54}
55
56#[derive(Debug, Clone)]
58pub enum SplitGeometry {
59 LineString(LineString),
61 Polygon(Polygon),
63}
64
65pub fn split_linestring_by_points(
103 linestring: &LineString,
104 split_points: &[Point],
105 options: &SplitOptions,
106) -> Result<SplitResult> {
107 if split_points.is_empty() {
108 return Ok(SplitResult {
109 geometries: vec![SplitGeometry::LineString(linestring.clone())],
110 num_splits: 0,
111 complete: true,
112 });
113 }
114
115 let coords = &linestring.coords;
116 if coords.len() < 2 {
117 return Err(AlgorithmError::InvalidGeometry(
118 "Linestring must have at least 2 coordinates".to_string(),
119 ));
120 }
121
122 let mut split_locations = Vec::new();
124
125 for point in split_points {
126 if let Some(location) = find_point_on_linestring(linestring, point, options.tolerance)? {
127 split_locations.push(location);
128 }
129 }
130
131 for loc in &split_locations {
134 if loc.parameter.is_nan() {
135 return Err(AlgorithmError::InvalidGeometry(
136 "Split location parameter contains NaN value".to_string(),
137 ));
138 }
139 }
140
141 split_locations.sort_by(|a, b| {
142 a.segment_index.cmp(&b.segment_index).then(
143 a.parameter
144 .partial_cmp(&b.parameter)
145 .unwrap_or(std::cmp::Ordering::Equal),
146 )
147 });
148
149 split_locations.dedup_by(|a, b| {
151 a.segment_index == b.segment_index && (a.parameter - b.parameter).abs() < options.tolerance
152 });
153
154 if split_locations.is_empty() {
155 return Ok(SplitResult {
156 geometries: vec![SplitGeometry::LineString(linestring.clone())],
157 num_splits: 0,
158 complete: false,
159 });
160 }
161
162 let mut result_geometries = Vec::new();
164 let mut current_coords = Vec::new();
165 let mut current_segment_idx = 0;
166 let mut split_idx = 0;
167
168 current_coords.push(coords[0]);
169
170 for i in 0..coords.len().saturating_sub(1) {
171 while split_idx < split_locations.len() && split_locations[split_idx].segment_index == i {
173 let split_loc = &split_locations[split_idx];
174 let split_coord = split_loc.coordinate;
175
176 current_coords.push(split_coord);
178
179 if current_coords.len() >= 2 {
181 if let Ok(ls) = LineString::new(current_coords.clone()) {
182 if options.preserve_all
183 || compute_linestring_length(&ls) >= options.min_segment_length
184 {
185 result_geometries.push(SplitGeometry::LineString(ls));
186 }
187 }
188 }
189
190 current_coords.clear();
192 current_coords.push(split_coord);
193
194 split_idx += 1;
195 }
196
197 current_coords.push(coords[i + 1]);
199 current_segment_idx = i + 1;
200 }
201
202 if current_coords.len() >= 2 {
204 if let Ok(ls) = LineString::new(current_coords) {
205 if options.preserve_all || compute_linestring_length(&ls) >= options.min_segment_length
206 {
207 result_geometries.push(SplitGeometry::LineString(ls));
208 }
209 }
210 }
211
212 Ok(SplitResult {
213 geometries: result_geometries,
214 num_splits: split_locations.len(),
215 complete: true,
216 })
217}
218
219#[derive(Debug, Clone)]
221struct PointLocation {
222 segment_index: usize,
224 parameter: f64,
226 coordinate: Coordinate,
228}
229
230fn find_point_on_linestring(
232 linestring: &LineString,
233 point: &Point,
234 tolerance: f64,
235) -> Result<Option<PointLocation>> {
236 let coords = &linestring.coords;
237
238 for i in 0..coords.len().saturating_sub(1) {
239 let p1 = coords[i];
240 let p2 = coords[i + 1];
241
242 if let Some((param, coord)) = point_on_segment(point, &p1, &p2, tolerance) {
244 return Ok(Some(PointLocation {
245 segment_index: i,
246 parameter: param,
247 coordinate: coord,
248 }));
249 }
250 }
251
252 Ok(None)
253}
254
255fn point_on_segment(
257 point: &Point,
258 p1: &Coordinate,
259 p2: &Coordinate,
260 tolerance: f64,
261) -> Option<(f64, Coordinate)> {
262 let px = point.coord.x;
263 let py = point.coord.y;
264
265 let dx = p2.x - p1.x;
267 let dy = p2.y - p1.y;
268
269 let len_sq = dx * dx + dy * dy;
271
272 if len_sq < tolerance * tolerance {
273 return None;
275 }
276
277 let t = ((px - p1.x) * dx + (py - p1.y) * dy) / len_sq;
279
280 if t < -tolerance || t > 1.0 + tolerance {
282 return None;
283 }
284
285 let t = t.clamp(0.0, 1.0);
287
288 let closest_x = p1.x + t * dx;
290 let closest_y = p1.y + t * dy;
291
292 let dist_sq = (px - closest_x).powi(2) + (py - closest_y).powi(2);
294
295 if dist_sq < tolerance * tolerance {
296 Some((t, Coordinate::new_2d(closest_x, closest_y)))
297 } else {
298 None
299 }
300}
301
302fn compute_linestring_length(linestring: &LineString) -> f64 {
304 let coords = &linestring.coords;
305 let mut length = 0.0;
306
307 for i in 0..coords.len().saturating_sub(1) {
308 let dx = coords[i + 1].x - coords[i].x;
309 let dy = coords[i + 1].y - coords[i].y;
310 length += (dx * dx + dy * dy).sqrt();
311 }
312
313 length
314}
315
316pub fn split_polygon_by_line(
359 polygon: &Polygon,
360 split_line: &LineString,
361 options: &SplitOptions,
362) -> Result<SplitResult> {
363 let intersection_points = find_polygon_line_intersections(polygon, split_line, options)?;
365
366 if intersection_points.len() < 2 {
367 return Ok(SplitResult {
369 geometries: vec![SplitGeometry::Polygon(polygon.clone())],
370 num_splits: 0,
371 complete: false,
372 });
373 }
374
375 let result_polygons =
377 create_split_polygons(polygon, split_line, &intersection_points, options)?;
378
379 let geometries = result_polygons
380 .into_iter()
381 .map(SplitGeometry::Polygon)
382 .collect();
383
384 Ok(SplitResult {
385 geometries,
386 num_splits: intersection_points.len(),
387 complete: true,
388 })
389}
390
391fn find_polygon_line_intersections(
393 polygon: &Polygon,
394 line: &LineString,
395 options: &SplitOptions,
396) -> Result<Vec<Coordinate>> {
397 let mut intersections = Vec::new();
398
399 let exterior_intersections = find_linestring_intersections(&polygon.exterior, line, options)?;
401 intersections.extend(exterior_intersections);
402
403 for interior in &polygon.interiors {
405 let interior_intersections = find_linestring_intersections(interior, line, options)?;
406 intersections.extend(interior_intersections);
407 }
408
409 for coord in &intersections {
412 if coord.x.is_nan() || coord.y.is_nan() {
413 return Err(AlgorithmError::InvalidGeometry(
414 "Intersection coordinate contains NaN value".to_string(),
415 ));
416 }
417 }
418
419 intersections.sort_by(|a, b| {
420 a.x.partial_cmp(&b.x)
421 .unwrap_or(std::cmp::Ordering::Equal)
422 .then(a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal))
423 });
424
425 intersections.dedup_by(|a, b| {
426 (a.x - b.x).abs() < options.tolerance && (a.y - b.y).abs() < options.tolerance
427 });
428
429 Ok(intersections)
430}
431
432fn find_linestring_intersections(
434 line1: &LineString,
435 line2: &LineString,
436 options: &SplitOptions,
437) -> Result<Vec<Coordinate>> {
438 let mut intersections = Vec::new();
439 let coords1 = &line1.coords;
440 let coords2 = &line2.coords;
441
442 for i in 0..coords1.len().saturating_sub(1) {
443 for j in 0..coords2.len().saturating_sub(1) {
444 if let Some(intersection) = compute_segment_intersection(
445 &coords1[i],
446 &coords1[i + 1],
447 &coords2[j],
448 &coords2[j + 1],
449 options.tolerance,
450 ) {
451 intersections.push(intersection);
452 }
453 }
454 }
455
456 Ok(intersections)
457}
458
459fn compute_segment_intersection(
461 p1: &Coordinate,
462 p2: &Coordinate,
463 p3: &Coordinate,
464 p4: &Coordinate,
465 tolerance: f64,
466) -> Option<Coordinate> {
467 let d = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
468
469 if d.abs() < tolerance {
470 return None;
472 }
473
474 let t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / d;
475 let u = -((p1.x - p2.x) * (p1.y - p3.y) - (p1.y - p2.y) * (p1.x - p3.x)) / d;
476
477 if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
478 let x = p1.x + t * (p2.x - p1.x);
479 let y = p1.y + t * (p2.y - p1.y);
480 Some(Coordinate::new_2d(x, y))
481 } else {
482 None
483 }
484}
485
486#[derive(Clone, Debug)]
494struct HalfEdge {
495 origin: usize,
497 twin: usize,
499 next: usize,
501 prev: usize,
503}
504
505struct Dcel {
507 vertices: Vec<[f64; 2]>,
509 half_edges: Vec<HalfEdge>,
511}
512
513impl Dcel {
514 fn new() -> Self {
515 Self {
516 vertices: Vec::new(),
517 half_edges: Vec::new(),
518 }
519 }
520
521 fn add_vertex(&mut self, x: f64, y: f64) -> usize {
522 let idx = self.vertices.len();
523 self.vertices.push([x, y]);
524 idx
525 }
526
527 fn add_half_edge_pair(&mut self, a: usize, b: usize) -> (usize, usize) {
530 let he = self.half_edges.len();
531 let te = he + 1;
532 self.half_edges.push(HalfEdge {
533 origin: a,
534 twin: te,
535 next: he,
536 prev: he,
537 });
538 self.half_edges.push(HalfEdge {
539 origin: b,
540 twin: he,
541 next: te,
542 prev: te,
543 });
544 (he, te)
545 }
546}
547
548fn signed_area_ring(coords: &[[f64; 2]]) -> f64 {
553 let n = coords.len();
554 if n < 3 {
555 return 0.0;
556 }
557 let mut area = 0.0f64;
558 for i in 0..n {
559 let j = (i + 1) % n;
560 area += coords[i][0] * coords[j][1];
561 area -= coords[j][0] * coords[i][1];
562 }
563 area * 0.5
564}
565
566fn point_in_ring(px: f64, py: f64, ring: &[[f64; 2]]) -> bool {
568 let mut inside = false;
569 let n = ring.len();
570 let mut j = n.wrapping_sub(1);
571 for i in 0..n {
572 let xi = ring[i][0];
573 let yi = ring[i][1];
574 let xj = ring[j][0];
575 let yj = ring[j][1];
576 if ((yi > py) != (yj > py)) && (px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
577 inside = !inside;
578 }
579 j = i;
580 }
581 inside
582}
583
584#[inline]
586fn dist2(a: [f64; 2], b: [f64; 2]) -> f64 {
587 let dx = a[0] - b[0];
588 let dy = a[1] - b[1];
589 dx * dx + dy * dy
590}
591
592fn find_or_add_vertex(dcel: &mut Dcel, x: f64, y: f64, tol_sq: f64) -> usize {
596 let pt = [x, y];
597 if let Some(idx) = dcel.vertices.iter().position(|v| dist2(*v, pt) <= tol_sq) {
598 return idx;
599 }
600 dcel.add_vertex(x, y)
601}
602
603fn walk_face_cycle(dcel: &Dcel, start_he: usize) -> Vec<[f64; 2]> {
605 let max_steps = dcel.half_edges.len() + 1;
606 let mut coords = Vec::new();
607 let mut cur = start_he;
608 for _ in 0..max_steps {
609 coords.push(dcel.vertices[dcel.half_edges[cur].origin]);
610 cur = dcel.half_edges[cur].next;
611 if cur == start_he {
612 break;
613 }
614 }
615 coords
616}
617
618fn collect_face_cycles(dcel: &Dcel) -> Vec<Vec<[f64; 2]>> {
620 let n = dcel.half_edges.len();
621 let mut visited = vec![false; n];
622 let mut faces = Vec::new();
623 for start in 0..n {
624 if visited[start] {
625 continue;
626 }
627 let cycle = walk_face_cycle(dcel, start);
628 let mut cur = start;
629 for _ in 0..cycle.len() {
630 visited[cur] = true;
631 cur = dcel.half_edges[cur].next;
632 }
633 if cycle.len() >= 3 {
634 faces.push(cycle);
635 }
636 }
637 faces
638}
639
640fn split_half_edge(dcel: &mut Dcel, ring_hes: &mut Vec<usize>, ring_pos: usize, m: usize) {
648 let he_ab = ring_hes[ring_pos];
649 let he_ba = dcel.half_edges[he_ab].twin;
650 let b = dcel.half_edges[he_ba].origin;
651
652 let next_ab = dcel.half_edges[he_ab].next;
654 let prev_ba = dcel.half_edges[he_ba].prev;
655
656 let (he_mb, he_bm) = dcel.add_half_edge_pair(m, b);
658
659 dcel.half_edges[he_ba].origin = m; dcel.half_edges[he_ab].next = he_mb;
664 dcel.half_edges[he_mb].prev = he_ab;
665 dcel.half_edges[he_mb].next = next_ab;
666 dcel.half_edges[next_ab].prev = he_mb;
667
668 dcel.half_edges[he_bm].prev = prev_ba;
670 dcel.half_edges[prev_ba].next = he_bm;
671 dcel.half_edges[he_bm].next = he_ba;
672 dcel.half_edges[he_ba].prev = he_bm;
673
674 ring_hes.insert(ring_pos + 1, he_mb);
676}
677
678fn splice_diagonal(
684 dcel: &mut Dcel,
685 he_into_a: usize,
686 he_into_b: usize,
687 he_ab: usize,
688 he_ba: usize,
689) {
690 let after_a = dcel.half_edges[he_into_a].next;
693 let after_b = dcel.half_edges[he_into_b].next;
694
695 dcel.half_edges[he_into_a].next = he_ab;
697 dcel.half_edges[he_ab].prev = he_into_a;
698 dcel.half_edges[he_ab].next = after_b;
699 dcel.half_edges[after_b].prev = he_ab;
700
701 dcel.half_edges[he_into_b].next = he_ba;
703 dcel.half_edges[he_ba].prev = he_into_b;
704 dcel.half_edges[he_ba].next = after_a;
705 dcel.half_edges[after_a].prev = he_ba;
706}
707
708fn find_incoming_he(dcel: &Dcel, vid: usize, ring_hes: &[usize]) -> Option<usize> {
712 for &he in ring_hes {
714 if dcel.half_edges[dcel.half_edges[he].twin].origin == vid {
715 return Some(he);
716 }
717 }
718 dcel.half_edges
720 .iter()
721 .enumerate()
722 .find(|(_, he)| dcel.half_edges[he.twin].origin == vid)
723 .map(|(i, _)| i)
724}
725
726fn create_split_polygons(
740 polygon: &Polygon,
741 split_line: &LineString,
742 intersections: &[Coordinate],
743 options: &SplitOptions,
744) -> Result<Vec<Polygon>> {
745 let tol = options.tolerance;
746 let tol_sq = tol * tol;
747
748 let ext_raw: Vec<[f64; 2]> = polygon.exterior.coords.iter().map(|c| [c.x, c.y]).collect();
750
751 let n_ring = ext_raw.len();
753 if n_ring < 4 {
754 return Ok(vec![polygon.clone()]);
755 }
756 let n_verts = n_ring - 1; let mut dcel = Dcel::new();
759
760 let ring_vids: Vec<usize> = (0..n_verts)
762 .map(|i| dcel.add_vertex(ext_raw[i][0], ext_raw[i][1]))
763 .collect();
764
765 let mut ring_hes: Vec<usize> = Vec::with_capacity(n_verts);
768 let mut twin_hes: Vec<usize> = Vec::with_capacity(n_verts);
769 for i in 0..n_verts {
770 let a = ring_vids[i];
771 let b = ring_vids[(i + 1) % n_verts];
772 let (he, te) = dcel.add_half_edge_pair(a, b);
773 ring_hes.push(he);
774 twin_hes.push(te);
775 }
776 for i in 0..n_verts {
778 let next_i = (i + 1) % n_verts;
779 let prev_i = (i + n_verts - 1) % n_verts;
780 dcel.half_edges[ring_hes[i]].next = ring_hes[next_i];
781 dcel.half_edges[ring_hes[i]].prev = ring_hes[prev_i];
782 }
783 for i in 0..n_verts {
785 let next_i = (i + n_verts - 1) % n_verts; let prev_i = (i + 1) % n_verts;
787 dcel.half_edges[twin_hes[i]].next = twin_hes[next_i];
788 dcel.half_edges[twin_hes[i]].prev = twin_hes[prev_i];
789 }
790
791 let split_raw: Vec<[f64; 2]> = split_line.coords.iter().map(|c| [c.x, c.y]).collect();
793 let split_n = split_raw.len();
794
795 let mut cumlen = vec![0.0f64; split_n];
797 for i in 1..split_n {
798 let dx = split_raw[i][0] - split_raw[i - 1][0];
799 let dy = split_raw[i][1] - split_raw[i - 1][1];
800 cumlen[i] = cumlen[i - 1] + (dx * dx + dy * dy).sqrt();
801 }
802
803 struct IntPt {
805 coord: [f64; 2],
806 t: f64,
807 }
808
809 let mut int_pts: Vec<IntPt> = Vec::new();
810 'classify: for isect in intersections {
811 let ix = isect.x;
812 let iy = isect.y;
813 for seg in 0..split_n.saturating_sub(1) {
814 let [ax, ay] = split_raw[seg];
815 let [bx, by] = split_raw[seg + 1];
816 let ddx = bx - ax;
817 let ddy = by - ay;
818 let seg_len_sq = ddx * ddx + ddy * ddy;
819 if seg_len_sq < tol_sq {
820 continue;
821 }
822 let s = ((ix - ax) * ddx + (iy - ay) * ddy) / seg_len_sq;
823 if s < -tol || s > 1.0 + tol {
824 continue;
825 }
826 let proj_x = ax + s * ddx;
827 let proj_y = ay + s * ddy;
828 if (ix - proj_x).powi(2) + (iy - proj_y).powi(2) > tol_sq * 100.0 {
829 continue;
830 }
831 let s_clamped = s.clamp(0.0, 1.0);
832 let t = cumlen[seg] + s_clamped * (cumlen[seg + 1] - cumlen[seg]);
833 int_pts.push(IntPt { coord: [ix, iy], t });
834 continue 'classify;
835 }
836 }
837
838 int_pts.sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal));
839 int_pts.dedup_by(|a, b| dist2(a.coord, b.coord) < tol_sq * 100.0);
840
841 if int_pts.len() < 2 {
842 return Ok(vec![polygon.clone()]);
843 }
844
845 let mut int_vids: Vec<usize> = Vec::with_capacity(int_pts.len());
851
852 for ip in &int_pts {
853 let [ix, iy] = ip.coord;
854
855 let mut found_pos: Option<usize> = None;
857 'find_edge: for pos in 0..ring_hes.len() {
858 let he = ring_hes[pos];
859 let a_vid = dcel.half_edges[he].origin;
860 let b_vid = dcel.half_edges[dcel.half_edges[he].twin].origin;
861 let [ax, ay] = dcel.vertices[a_vid];
862 let [bx, by] = dcel.vertices[b_vid];
863 let ddx = bx - ax;
864 let ddy = by - ay;
865 let seg_len_sq = ddx * ddx + ddy * ddy;
866 if seg_len_sq < tol_sq {
867 continue;
868 }
869 let s = ((ix - ax) * ddx + (iy - ay) * ddy) / seg_len_sq;
870 if s < -tol || s > 1.0 + tol {
871 continue;
872 }
873 let proj_x = ax + s * ddx;
874 let proj_y = ay + s * ddy;
875 if (ix - proj_x).powi(2) + (iy - proj_y).powi(2) <= tol_sq * 100.0 {
876 found_pos = Some(pos);
877 break 'find_edge;
878 }
879 }
880
881 let m_vid = find_or_add_vertex(&mut dcel, ix, iy, tol_sq * 100.0);
883 int_vids.push(m_vid);
884
885 if let Some(pos) = found_pos {
886 let he = ring_hes[pos];
888 let a_vid = dcel.half_edges[he].origin;
889 let b_vid = dcel.half_edges[dcel.half_edges[he].twin].origin;
890 if m_vid == a_vid || m_vid == b_vid {
891 continue; }
893 split_half_edge(&mut dcel, &mut ring_hes, pos, m_vid);
894 }
895 }
896
897 for k in 0..int_pts.len().saturating_sub(1) {
902 let [ax, ay] = int_pts[k].coord;
903 let [bx, by] = int_pts[k + 1].coord;
904 let mx = (ax + bx) * 0.5;
905 let my = (ay + by) * 0.5;
906
907 if !point_in_ring(mx, my, &ext_raw[..n_verts]) {
909 continue;
910 }
911 let in_hole = polygon.interiors.iter().any(|h| {
913 let hole_raw: Vec<[f64; 2]> = h.coords.iter().map(|c| [c.x, c.y]).collect();
914 let n_h = hole_raw.len().saturating_sub(1); point_in_ring(mx, my, &hole_raw[..n_h])
916 });
917 if in_hole {
918 continue;
919 }
920
921 let vid_a = int_vids[k];
922 let vid_b = int_vids[k + 1];
923 if vid_a == vid_b {
924 continue;
925 }
926
927 let into_a = match find_incoming_he(&dcel, vid_a, &ring_hes) {
929 Some(h) => h,
930 None => continue,
931 };
932 let into_b = match find_incoming_he(&dcel, vid_b, &ring_hes) {
933 Some(h) => h,
934 None => continue,
935 };
936
937 let (he_ab, he_ba) = dcel.add_half_edge_pair(vid_a, vid_b);
939
940 splice_diagonal(&mut dcel, into_a, into_b, he_ab, he_ba);
942 }
943
944 let face_cycles = collect_face_cycles(&dcel);
946 if face_cycles.is_empty() {
947 return Ok(vec![polygon.clone()]);
948 }
949
950 let outer_idx = face_cycles
952 .iter()
953 .enumerate()
954 .min_by(|(_, a), (_, b)| {
955 signed_area_ring(a)
956 .partial_cmp(&signed_area_ring(b))
957 .unwrap_or(std::cmp::Ordering::Equal)
958 })
959 .map(|(i, _)| i)
960 .unwrap_or(usize::MAX);
961
962 let mut result_polygons: Vec<Polygon> = Vec::new();
964
965 for (i, cycle) in face_cycles.iter().enumerate() {
966 if i == outer_idx {
967 continue;
968 }
969 let sa = signed_area_ring(cycle);
970 if sa.abs() < tol * tol {
971 continue; }
973
974 let mut ring_pts = cycle.clone();
976 ring_pts.push(ring_pts[0]);
977
978 if ring_pts.len() < 4 {
979 continue;
980 }
981
982 if options.preserve_orientation && sa < 0.0 {
984 ring_pts.reverse();
985 }
986
987 let coords: Vec<Coordinate> = ring_pts
988 .iter()
989 .map(|&[x, y]| Coordinate::new_2d(x, y))
990 .collect();
991
992 let exterior = match LineString::new(coords) {
993 Ok(ls) => ls,
994 Err(_) => continue,
995 };
996
997 let poly_area = sa.abs();
998 if let Some(threshold) = options.min_area {
999 if poly_area < threshold {
1000 continue;
1001 }
1002 }
1003
1004 let mut interior_rings: Vec<LineString> = Vec::new();
1006 if options.keep_holes {
1007 for hole in &polygon.interiors {
1008 if hole.coords.len() < 4 {
1009 continue;
1010 }
1011 let hx = hole.coords[0].x;
1012 let hy = hole.coords[0].y;
1013 if point_in_ring(hx, hy, cycle) {
1014 interior_rings.push(hole.clone());
1015 }
1016 }
1017 }
1018
1019 let poly = match Polygon::new(exterior, interior_rings) {
1020 Ok(p) => p,
1021 Err(_) => continue,
1022 };
1023 result_polygons.push(poly);
1024 }
1025
1026 if result_polygons.is_empty() {
1027 return Ok(vec![polygon.clone()]);
1028 }
1029
1030 Ok(result_polygons)
1031}
1032
1033pub fn split_polygon_by_polygon(
1037 target_poly: &Polygon,
1038 split_poly: &Polygon,
1039 options: &SplitOptions,
1040) -> Result<SplitResult> {
1041 let split_line = &split_poly.exterior.clone();
1043
1044 split_polygon_by_line(target_poly, split_line, options)
1045}
1046
1047pub fn split_polygons_batch(
1049 polygons: &[Polygon],
1050 split_line: &LineString,
1051 options: &SplitOptions,
1052) -> Result<Vec<SplitResult>> {
1053 polygons
1054 .iter()
1055 .map(|poly| split_polygon_by_line(poly, split_line, options))
1056 .collect()
1057}
1058
1059#[cfg(test)]
1060mod tests {
1061 use super::*;
1062
1063 fn create_linestring(coords: Vec<(f64, f64)>) -> LineString {
1064 let coords: Vec<Coordinate> = coords
1065 .iter()
1066 .map(|(x, y)| Coordinate::new_2d(*x, *y))
1067 .collect();
1068 LineString::new(coords).expect("Failed to create linestring")
1069 }
1070
1071 fn create_polygon(coords: Vec<(f64, f64)>) -> Polygon {
1072 let coords: Vec<Coordinate> = coords
1073 .iter()
1074 .map(|(x, y)| Coordinate::new_2d(*x, *y))
1075 .collect();
1076 let exterior = LineString::new(coords).expect("Failed to create linestring");
1077 Polygon::new(exterior, vec![]).expect("Failed to create polygon")
1078 }
1079
1080 #[test]
1081 fn test_split_linestring_single_point() {
1082 let linestring = create_linestring(vec![(0.0, 0.0), (10.0, 0.0)]);
1083 let split_points = vec![Point::new(5.0, 0.0)];
1084
1085 let result =
1086 split_linestring_by_points(&linestring, &split_points, &SplitOptions::default());
1087 assert!(result.is_ok());
1088
1089 let split_result = result.expect("Split failed");
1090 assert_eq!(split_result.num_splits, 1);
1091 assert!(split_result.geometries.len() >= 2);
1092 }
1093
1094 #[test]
1095 fn test_split_linestring_multiple_points() {
1096 let linestring = create_linestring(vec![(0.0, 0.0), (10.0, 0.0)]);
1097 let split_points = vec![Point::new(3.0, 0.0), Point::new(7.0, 0.0)];
1098
1099 let result =
1100 split_linestring_by_points(&linestring, &split_points, &SplitOptions::default());
1101 assert!(result.is_ok());
1102
1103 let split_result = result.expect("Split failed");
1104 assert_eq!(split_result.num_splits, 2);
1105 }
1106
1107 #[test]
1108 fn test_split_linestring_no_intersection() {
1109 let linestring = create_linestring(vec![(0.0, 0.0), (10.0, 0.0)]);
1110 let split_points = vec![Point::new(5.0, 5.0)]; let result =
1113 split_linestring_by_points(&linestring, &split_points, &SplitOptions::default());
1114 assert!(result.is_ok());
1115
1116 let split_result = result.expect("Split failed");
1117 assert_eq!(split_result.num_splits, 0);
1118 assert_eq!(split_result.geometries.len(), 1); }
1120
1121 #[test]
1122 fn test_split_linestring_empty_splits() {
1123 let linestring = create_linestring(vec![(0.0, 0.0), (10.0, 0.0)]);
1124 let split_points = vec![];
1125
1126 let result =
1127 split_linestring_by_points(&linestring, &split_points, &SplitOptions::default());
1128 assert!(result.is_ok());
1129
1130 let split_result = result.expect("Split failed");
1131 assert_eq!(split_result.num_splits, 0);
1132 assert_eq!(split_result.geometries.len(), 1);
1133 }
1134
1135 #[test]
1136 fn test_point_on_segment() {
1137 let p1 = Coordinate::new_2d(0.0, 0.0);
1138 let p2 = Coordinate::new_2d(10.0, 0.0);
1139 let point = Point::new(5.0, 0.0);
1140
1141 let result = point_on_segment(&point, &p1, &p2, 1e-10);
1142 assert!(result.is_some());
1143
1144 if let Some((param, coord)) = result {
1145 assert!((param - 0.5).abs() < 1e-10);
1146 assert!((coord.x - 5.0).abs() < 1e-10);
1147 assert!((coord.y - 0.0).abs() < 1e-10);
1148 }
1149 }
1150
1151 #[test]
1152 fn test_point_not_on_segment() {
1153 let p1 = Coordinate::new_2d(0.0, 0.0);
1154 let p2 = Coordinate::new_2d(10.0, 0.0);
1155 let point = Point::new(5.0, 5.0); let result = point_on_segment(&point, &p1, &p2, 1e-10);
1158 assert!(result.is_none());
1159 }
1160
1161 #[test]
1162 fn test_compute_segment_intersection() {
1163 let p1 = Coordinate::new_2d(0.0, 0.0);
1164 let p2 = Coordinate::new_2d(10.0, 10.0);
1165 let p3 = Coordinate::new_2d(0.0, 10.0);
1166 let p4 = Coordinate::new_2d(10.0, 0.0);
1167
1168 let result = compute_segment_intersection(&p1, &p2, &p3, &p4, 1e-10);
1169 assert!(result.is_some());
1170
1171 if let Some(intersection) = result {
1172 assert!((intersection.x - 5.0).abs() < 1e-6);
1174 assert!((intersection.y - 5.0).abs() < 1e-6);
1175 }
1176 }
1177
1178 #[test]
1179 fn test_split_polygon_by_line() {
1180 let polygon = create_polygon(vec![
1181 (0.0, 0.0),
1182 (10.0, 0.0),
1183 (10.0, 10.0),
1184 (0.0, 10.0),
1185 (0.0, 0.0),
1186 ]);
1187
1188 let split_line = create_linestring(vec![(0.0, 5.0), (10.0, 5.0)]);
1189
1190 let result = split_polygon_by_line(&polygon, &split_line, &SplitOptions::default());
1191 assert!(result.is_ok());
1192
1193 let split_result = result.expect("Split failed");
1194 assert_eq!(split_result.num_splits, 2); }
1196
1197 #[test]
1198 fn test_compute_linestring_length() {
1199 let linestring = create_linestring(vec![(0.0, 0.0), (3.0, 0.0), (3.0, 4.0)]);
1200 let length = compute_linestring_length(&linestring);
1201
1202 assert!((length - 7.0).abs() < 1e-6);
1204 }
1205
1206 fn polygon_area(poly: &Polygon) -> f64 {
1210 let coords = &poly.exterior.coords;
1211 let n = coords.len();
1212 if n < 3 {
1213 return 0.0;
1214 }
1215 let mut area = 0.0f64;
1216 let mut j = n - 1;
1217 for i in 0..n {
1218 area += (coords[j].x + coords[i].x) * (coords[j].y - coords[i].y);
1219 j = i;
1220 }
1221 (area * 0.5).abs()
1222 }
1223
1224 #[test]
1225 fn test_split_square_by_vertical_returns_two_halves() {
1226 let polygon = create_polygon(vec![
1228 (0.0, 0.0),
1229 (1.0, 0.0),
1230 (1.0, 1.0),
1231 (0.0, 1.0),
1232 (0.0, 0.0),
1233 ]);
1234 let split_line = create_linestring(vec![(-0.5, 0.5), (1.5, 0.5)]);
1235
1236 let result = split_polygon_by_line(&polygon, &split_line, &SplitOptions::default())
1237 .expect("split must succeed for vertical line on unit square");
1238
1239 assert_eq!(
1241 result.geometries.len(),
1242 2,
1243 "vertical split of unit square must yield exactly 2 pieces, got {}",
1244 result.geometries.len()
1245 );
1246
1247 let areas: Vec<f64> = result
1248 .geometries
1249 .iter()
1250 .filter_map(|g| {
1251 if let SplitGeometry::Polygon(p) = g {
1252 Some(polygon_area(p))
1253 } else {
1254 None
1255 }
1256 })
1257 .collect();
1258 assert_eq!(
1259 areas.len(),
1260 result.geometries.len(),
1261 "all geometries must be polygons"
1262 );
1263 for area in &areas {
1264 assert!(
1265 (area - 0.5).abs() < 1e-6,
1266 "each half must have area ≈ 0.5, got {area}"
1267 );
1268 }
1269 }
1270
1271 #[test]
1272 fn test_split_square_by_diagonal_returns_two_triangles() {
1273 let polygon = create_polygon(vec![
1276 (0.0, 0.0),
1277 (1.0, 0.0),
1278 (1.0, 1.0),
1279 (0.0, 1.0),
1280 (0.0, 0.0),
1281 ]);
1282 let split_line = create_linestring(vec![(-0.1, -0.1), (1.1, 1.1)]);
1283
1284 let result = split_polygon_by_line(&polygon, &split_line, &SplitOptions::default())
1285 .expect("split must succeed for diagonal split");
1286
1287 assert_eq!(
1288 result.geometries.len(),
1289 2,
1290 "diagonal split must yield 2 triangles, got {}",
1291 result.geometries.len()
1292 );
1293
1294 let areas: Vec<f64> = result
1295 .geometries
1296 .iter()
1297 .filter_map(|g| {
1298 if let SplitGeometry::Polygon(p) = g {
1299 Some(polygon_area(p))
1300 } else {
1301 None
1302 }
1303 })
1304 .collect();
1305 assert_eq!(areas.len(), 2, "both geometries must be polygons");
1306 for area in &areas {
1307 assert!(
1308 (area - 0.5).abs() < 1e-5,
1309 "each triangle must have area ≈ 0.5, got {area}"
1310 );
1311 }
1312 }
1313
1314 #[test]
1315 fn test_split_line_misses_polygon_returns_original() {
1316 let polygon = create_polygon(vec![
1318 (0.0, 0.0),
1319 (1.0, 0.0),
1320 (1.0, 1.0),
1321 (0.0, 1.0),
1322 (0.0, 0.0),
1323 ]);
1324 let split_line = create_linestring(vec![(5.0, 0.0), (5.0, 1.0)]);
1325
1326 let result = split_polygon_by_line(&polygon, &split_line, &SplitOptions::default())
1327 .expect("split must succeed even when line misses polygon");
1328
1329 assert_eq!(
1330 result.geometries.len(),
1331 1,
1332 "missed line must return exactly the original polygon"
1333 );
1334 assert_eq!(
1335 result.num_splits, 0,
1336 "no split points should be recorded for a miss"
1337 );
1338 }
1339
1340 #[test]
1341 fn test_split_concave_polygon_three_pieces() {
1342 let polygon = create_polygon(vec![
1347 (0.0, 0.0),
1348 (2.0, 0.0),
1349 (2.0, 1.0),
1350 (1.0, 1.0),
1351 (1.0, 2.0),
1352 (0.0, 2.0),
1353 (0.0, 0.0),
1354 ]);
1355 let split_line = create_linestring(vec![(-0.5, 1.0), (2.5, 1.0)]);
1356
1357 let result = split_polygon_by_line(&polygon, &split_line, &SplitOptions::default())
1358 .expect("split must succeed for L-shaped polygon");
1359
1360 assert!(
1361 !result.geometries.is_empty(),
1362 "split must return at least one polygon piece"
1363 );
1364
1365 for geom in &result.geometries {
1366 if let SplitGeometry::Polygon(poly) = geom {
1367 let area = polygon_area(poly);
1368 assert!(
1369 area > 1e-10,
1370 "all result pieces must have positive area, got {area}"
1371 );
1372 }
1373 }
1375 }
1376
1377 #[test]
1378 fn test_split_respects_min_area_filter() {
1379 let polygon = create_polygon(vec![
1382 (0.0, 0.0),
1383 (1.0, 0.0),
1384 (1.0, 1.0),
1385 (0.0, 1.0),
1386 (0.0, 0.0),
1387 ]);
1388 let split_line = create_linestring(vec![(-0.5, 0.5), (1.5, 0.5)]);
1389
1390 let options = SplitOptions {
1391 min_area: Some(1.0), ..SplitOptions::default()
1393 };
1394
1395 let result = split_polygon_by_line(&polygon, &split_line, &options)
1396 .expect("split must succeed with min_area filter");
1397
1398 for geom in &result.geometries {
1402 if let SplitGeometry::Polygon(poly) = geom {
1403 let area = polygon_area(poly);
1404 assert!(
1405 area >= 1.0,
1406 "min_area filter must remove all pieces smaller than 1.0, piece area = {area}"
1407 );
1408 }
1409 }
1410 }
1411}