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}
23
24impl Default for SplitOptions {
25 fn default() -> Self {
26 Self {
27 tolerance: 1e-10,
28 snap_to_grid: false,
29 grid_size: 1e-6,
30 min_segment_length: 0.0,
31 preserve_all: true,
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct SplitResult {
39 pub geometries: Vec<SplitGeometry>,
41 pub num_splits: usize,
43 pub complete: bool,
45}
46
47#[derive(Debug, Clone)]
49pub enum SplitGeometry {
50 LineString(LineString),
52 Polygon(Polygon),
54}
55
56pub fn split_linestring_by_points(
94 linestring: &LineString,
95 split_points: &[Point],
96 options: &SplitOptions,
97) -> Result<SplitResult> {
98 if split_points.is_empty() {
99 return Ok(SplitResult {
100 geometries: vec![SplitGeometry::LineString(linestring.clone())],
101 num_splits: 0,
102 complete: true,
103 });
104 }
105
106 let coords = &linestring.coords;
107 if coords.len() < 2 {
108 return Err(AlgorithmError::InvalidGeometry(
109 "Linestring must have at least 2 coordinates".to_string(),
110 ));
111 }
112
113 let mut split_locations = Vec::new();
115
116 for point in split_points {
117 if let Some(location) = find_point_on_linestring(linestring, point, options.tolerance)? {
118 split_locations.push(location);
119 }
120 }
121
122 for loc in &split_locations {
125 if loc.parameter.is_nan() {
126 return Err(AlgorithmError::InvalidGeometry(
127 "Split location parameter contains NaN value".to_string(),
128 ));
129 }
130 }
131
132 split_locations.sort_by(|a, b| {
133 a.segment_index.cmp(&b.segment_index).then(
134 a.parameter
135 .partial_cmp(&b.parameter)
136 .unwrap_or(std::cmp::Ordering::Equal),
137 )
138 });
139
140 split_locations.dedup_by(|a, b| {
142 a.segment_index == b.segment_index && (a.parameter - b.parameter).abs() < options.tolerance
143 });
144
145 if split_locations.is_empty() {
146 return Ok(SplitResult {
147 geometries: vec![SplitGeometry::LineString(linestring.clone())],
148 num_splits: 0,
149 complete: false,
150 });
151 }
152
153 let mut result_geometries = Vec::new();
155 let mut current_coords = Vec::new();
156 let mut current_segment_idx = 0;
157 let mut split_idx = 0;
158
159 current_coords.push(coords[0]);
160
161 for i in 0..coords.len().saturating_sub(1) {
162 while split_idx < split_locations.len() && split_locations[split_idx].segment_index == i {
164 let split_loc = &split_locations[split_idx];
165 let split_coord = split_loc.coordinate;
166
167 current_coords.push(split_coord);
169
170 if current_coords.len() >= 2 {
172 if let Ok(ls) = LineString::new(current_coords.clone()) {
173 if options.preserve_all
174 || compute_linestring_length(&ls) >= options.min_segment_length
175 {
176 result_geometries.push(SplitGeometry::LineString(ls));
177 }
178 }
179 }
180
181 current_coords.clear();
183 current_coords.push(split_coord);
184
185 split_idx += 1;
186 }
187
188 current_coords.push(coords[i + 1]);
190 current_segment_idx = i + 1;
191 }
192
193 if current_coords.len() >= 2 {
195 if let Ok(ls) = LineString::new(current_coords) {
196 if options.preserve_all || compute_linestring_length(&ls) >= options.min_segment_length
197 {
198 result_geometries.push(SplitGeometry::LineString(ls));
199 }
200 }
201 }
202
203 Ok(SplitResult {
204 geometries: result_geometries,
205 num_splits: split_locations.len(),
206 complete: true,
207 })
208}
209
210#[derive(Debug, Clone)]
212struct PointLocation {
213 segment_index: usize,
215 parameter: f64,
217 coordinate: Coordinate,
219}
220
221fn find_point_on_linestring(
223 linestring: &LineString,
224 point: &Point,
225 tolerance: f64,
226) -> Result<Option<PointLocation>> {
227 let coords = &linestring.coords;
228
229 for i in 0..coords.len().saturating_sub(1) {
230 let p1 = coords[i];
231 let p2 = coords[i + 1];
232
233 if let Some((param, coord)) = point_on_segment(point, &p1, &p2, tolerance) {
235 return Ok(Some(PointLocation {
236 segment_index: i,
237 parameter: param,
238 coordinate: coord,
239 }));
240 }
241 }
242
243 Ok(None)
244}
245
246fn point_on_segment(
248 point: &Point,
249 p1: &Coordinate,
250 p2: &Coordinate,
251 tolerance: f64,
252) -> Option<(f64, Coordinate)> {
253 let px = point.coord.x;
254 let py = point.coord.y;
255
256 let dx = p2.x - p1.x;
258 let dy = p2.y - p1.y;
259
260 let len_sq = dx * dx + dy * dy;
262
263 if len_sq < tolerance * tolerance {
264 return None;
266 }
267
268 let t = ((px - p1.x) * dx + (py - p1.y) * dy) / len_sq;
270
271 if t < -tolerance || t > 1.0 + tolerance {
273 return None;
274 }
275
276 let t = t.clamp(0.0, 1.0);
278
279 let closest_x = p1.x + t * dx;
281 let closest_y = p1.y + t * dy;
282
283 let dist_sq = (px - closest_x).powi(2) + (py - closest_y).powi(2);
285
286 if dist_sq < tolerance * tolerance {
287 Some((t, Coordinate::new_2d(closest_x, closest_y)))
288 } else {
289 None
290 }
291}
292
293fn compute_linestring_length(linestring: &LineString) -> f64 {
295 let coords = &linestring.coords;
296 let mut length = 0.0;
297
298 for i in 0..coords.len().saturating_sub(1) {
299 let dx = coords[i + 1].x - coords[i].x;
300 let dy = coords[i + 1].y - coords[i].y;
301 length += (dx * dx + dy * dy).sqrt();
302 }
303
304 length
305}
306
307pub fn split_polygon_by_line(
350 polygon: &Polygon,
351 split_line: &LineString,
352 options: &SplitOptions,
353) -> Result<SplitResult> {
354 let intersection_points = find_polygon_line_intersections(polygon, split_line, options)?;
356
357 if intersection_points.len() < 2 {
358 return Ok(SplitResult {
360 geometries: vec![SplitGeometry::Polygon(polygon.clone())],
361 num_splits: 0,
362 complete: false,
363 });
364 }
365
366 let result_polygons =
368 create_split_polygons(polygon, split_line, &intersection_points, options)?;
369
370 let geometries = result_polygons
371 .into_iter()
372 .map(SplitGeometry::Polygon)
373 .collect();
374
375 Ok(SplitResult {
376 geometries,
377 num_splits: intersection_points.len(),
378 complete: true,
379 })
380}
381
382fn find_polygon_line_intersections(
384 polygon: &Polygon,
385 line: &LineString,
386 options: &SplitOptions,
387) -> Result<Vec<Coordinate>> {
388 let mut intersections = Vec::new();
389
390 let exterior_intersections = find_linestring_intersections(&polygon.exterior, line, options)?;
392 intersections.extend(exterior_intersections);
393
394 for interior in &polygon.interiors {
396 let interior_intersections = find_linestring_intersections(interior, line, options)?;
397 intersections.extend(interior_intersections);
398 }
399
400 for coord in &intersections {
403 if coord.x.is_nan() || coord.y.is_nan() {
404 return Err(AlgorithmError::InvalidGeometry(
405 "Intersection coordinate contains NaN value".to_string(),
406 ));
407 }
408 }
409
410 intersections.sort_by(|a, b| {
411 a.x.partial_cmp(&b.x)
412 .unwrap_or(std::cmp::Ordering::Equal)
413 .then(a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal))
414 });
415
416 intersections.dedup_by(|a, b| {
417 (a.x - b.x).abs() < options.tolerance && (a.y - b.y).abs() < options.tolerance
418 });
419
420 Ok(intersections)
421}
422
423fn find_linestring_intersections(
425 line1: &LineString,
426 line2: &LineString,
427 options: &SplitOptions,
428) -> Result<Vec<Coordinate>> {
429 let mut intersections = Vec::new();
430 let coords1 = &line1.coords;
431 let coords2 = &line2.coords;
432
433 for i in 0..coords1.len().saturating_sub(1) {
434 for j in 0..coords2.len().saturating_sub(1) {
435 if let Some(intersection) = compute_segment_intersection(
436 &coords1[i],
437 &coords1[i + 1],
438 &coords2[j],
439 &coords2[j + 1],
440 options.tolerance,
441 ) {
442 intersections.push(intersection);
443 }
444 }
445 }
446
447 Ok(intersections)
448}
449
450fn compute_segment_intersection(
452 p1: &Coordinate,
453 p2: &Coordinate,
454 p3: &Coordinate,
455 p4: &Coordinate,
456 tolerance: f64,
457) -> Option<Coordinate> {
458 let d = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
459
460 if d.abs() < tolerance {
461 return None;
463 }
464
465 let t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / d;
466 let u = -((p1.x - p2.x) * (p1.y - p3.y) - (p1.y - p2.y) * (p1.x - p3.x)) / d;
467
468 if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
469 let x = p1.x + t * (p2.x - p1.x);
470 let y = p1.y + t * (p2.y - p1.y);
471 Some(Coordinate::new_2d(x, y))
472 } else {
473 None
474 }
475}
476
477fn create_split_polygons(
479 _polygon: &Polygon,
480 _split_line: &LineString,
481 _intersections: &[Coordinate],
482 _options: &SplitOptions,
483) -> Result<Vec<Polygon>> {
484 Ok(Vec::new())
492}
493
494pub fn split_polygon_by_polygon(
498 target_poly: &Polygon,
499 split_poly: &Polygon,
500 options: &SplitOptions,
501) -> Result<SplitResult> {
502 let split_line = &split_poly.exterior.clone();
504
505 split_polygon_by_line(target_poly, split_line, options)
506}
507
508pub fn split_polygons_batch(
510 polygons: &[Polygon],
511 split_line: &LineString,
512 options: &SplitOptions,
513) -> Result<Vec<SplitResult>> {
514 polygons
515 .iter()
516 .map(|poly| split_polygon_by_line(poly, split_line, options))
517 .collect()
518}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523
524 fn create_linestring(coords: Vec<(f64, f64)>) -> LineString {
525 let coords: Vec<Coordinate> = coords
526 .iter()
527 .map(|(x, y)| Coordinate::new_2d(*x, *y))
528 .collect();
529 LineString::new(coords).expect("Failed to create linestring")
530 }
531
532 fn create_polygon(coords: Vec<(f64, f64)>) -> Polygon {
533 let coords: Vec<Coordinate> = coords
534 .iter()
535 .map(|(x, y)| Coordinate::new_2d(*x, *y))
536 .collect();
537 let exterior = LineString::new(coords).expect("Failed to create linestring");
538 Polygon::new(exterior, vec![]).expect("Failed to create polygon")
539 }
540
541 #[test]
542 fn test_split_linestring_single_point() {
543 let linestring = create_linestring(vec![(0.0, 0.0), (10.0, 0.0)]);
544 let split_points = vec![Point::new(5.0, 0.0)];
545
546 let result =
547 split_linestring_by_points(&linestring, &split_points, &SplitOptions::default());
548 assert!(result.is_ok());
549
550 let split_result = result.expect("Split failed");
551 assert_eq!(split_result.num_splits, 1);
552 assert!(split_result.geometries.len() >= 2);
553 }
554
555 #[test]
556 fn test_split_linestring_multiple_points() {
557 let linestring = create_linestring(vec![(0.0, 0.0), (10.0, 0.0)]);
558 let split_points = vec![Point::new(3.0, 0.0), Point::new(7.0, 0.0)];
559
560 let result =
561 split_linestring_by_points(&linestring, &split_points, &SplitOptions::default());
562 assert!(result.is_ok());
563
564 let split_result = result.expect("Split failed");
565 assert_eq!(split_result.num_splits, 2);
566 }
567
568 #[test]
569 fn test_split_linestring_no_intersection() {
570 let linestring = create_linestring(vec![(0.0, 0.0), (10.0, 0.0)]);
571 let split_points = vec![Point::new(5.0, 5.0)]; let result =
574 split_linestring_by_points(&linestring, &split_points, &SplitOptions::default());
575 assert!(result.is_ok());
576
577 let split_result = result.expect("Split failed");
578 assert_eq!(split_result.num_splits, 0);
579 assert_eq!(split_result.geometries.len(), 1); }
581
582 #[test]
583 fn test_split_linestring_empty_splits() {
584 let linestring = create_linestring(vec![(0.0, 0.0), (10.0, 0.0)]);
585 let split_points = vec![];
586
587 let result =
588 split_linestring_by_points(&linestring, &split_points, &SplitOptions::default());
589 assert!(result.is_ok());
590
591 let split_result = result.expect("Split failed");
592 assert_eq!(split_result.num_splits, 0);
593 assert_eq!(split_result.geometries.len(), 1);
594 }
595
596 #[test]
597 fn test_point_on_segment() {
598 let p1 = Coordinate::new_2d(0.0, 0.0);
599 let p2 = Coordinate::new_2d(10.0, 0.0);
600 let point = Point::new(5.0, 0.0);
601
602 let result = point_on_segment(&point, &p1, &p2, 1e-10);
603 assert!(result.is_some());
604
605 if let Some((param, coord)) = result {
606 assert!((param - 0.5).abs() < 1e-10);
607 assert!((coord.x - 5.0).abs() < 1e-10);
608 assert!((coord.y - 0.0).abs() < 1e-10);
609 }
610 }
611
612 #[test]
613 fn test_point_not_on_segment() {
614 let p1 = Coordinate::new_2d(0.0, 0.0);
615 let p2 = Coordinate::new_2d(10.0, 0.0);
616 let point = Point::new(5.0, 5.0); let result = point_on_segment(&point, &p1, &p2, 1e-10);
619 assert!(result.is_none());
620 }
621
622 #[test]
623 fn test_compute_segment_intersection() {
624 let p1 = Coordinate::new_2d(0.0, 0.0);
625 let p2 = Coordinate::new_2d(10.0, 10.0);
626 let p3 = Coordinate::new_2d(0.0, 10.0);
627 let p4 = Coordinate::new_2d(10.0, 0.0);
628
629 let result = compute_segment_intersection(&p1, &p2, &p3, &p4, 1e-10);
630 assert!(result.is_some());
631
632 if let Some(intersection) = result {
633 assert!((intersection.x - 5.0).abs() < 1e-6);
635 assert!((intersection.y - 5.0).abs() < 1e-6);
636 }
637 }
638
639 #[test]
640 fn test_split_polygon_by_line() {
641 let polygon = create_polygon(vec![
642 (0.0, 0.0),
643 (10.0, 0.0),
644 (10.0, 10.0),
645 (0.0, 10.0),
646 (0.0, 0.0),
647 ]);
648
649 let split_line = create_linestring(vec![(0.0, 5.0), (10.0, 5.0)]);
650
651 let result = split_polygon_by_line(&polygon, &split_line, &SplitOptions::default());
652 assert!(result.is_ok());
653
654 let split_result = result.expect("Split failed");
655 assert_eq!(split_result.num_splits, 2); }
657
658 #[test]
659 fn test_compute_linestring_length() {
660 let linestring = create_linestring(vec![(0.0, 0.0), (3.0, 0.0), (3.0, 4.0)]);
661 let length = compute_linestring_length(&linestring);
662
663 assert!((length - 7.0).abs() < 1e-6);
665 }
666}