1use crate::error::{AlgorithmError, Result};
39use oxigdal_core::vector::{Coordinate, LineString, Polygon};
40
41#[cfg(feature = "std")]
42use std::vec::Vec;
43
44#[derive(Debug, Clone)]
46pub struct RepairOptions {
47 pub remove_duplicates: bool,
49 pub fix_orientation: bool,
51 pub close_rings: bool,
53 pub remove_spikes: bool,
55 pub remove_collinear: bool,
57 pub tolerance: f64,
59}
60
61impl Default for RepairOptions {
62 fn default() -> Self {
63 Self {
64 remove_duplicates: true,
65 fix_orientation: true,
66 close_rings: true,
67 remove_spikes: true,
68 remove_collinear: false,
69 tolerance: f64::EPSILON,
70 }
71 }
72}
73
74impl RepairOptions {
75 pub fn all() -> Self {
77 Self {
78 remove_duplicates: true,
79 fix_orientation: true,
80 close_rings: true,
81 remove_spikes: true,
82 remove_collinear: true,
83 tolerance: f64::EPSILON,
84 }
85 }
86
87 pub fn basic() -> Self {
89 Self {
90 remove_duplicates: true,
91 fix_orientation: false,
92 close_rings: true,
93 remove_spikes: false,
94 remove_collinear: false,
95 tolerance: f64::EPSILON,
96 }
97 }
98
99 pub fn with_tolerance(mut self, tolerance: f64) -> Self {
101 self.tolerance = tolerance;
102 self
103 }
104}
105
106pub fn repair_polygon(polygon: &Polygon) -> Result<Polygon> {
120 repair_polygon_with_options(polygon, &RepairOptions::default())
121}
122
123pub fn repair_polygon_with_options(polygon: &Polygon, options: &RepairOptions) -> Result<Polygon> {
138 let exterior_coords = repair_ring(&polygon.exterior.coords, true, options)?;
140 let exterior = LineString::new(exterior_coords).map_err(|e| AlgorithmError::GeometryError {
141 message: format!("Failed to create exterior ring: {}", e),
142 })?;
143
144 let mut interiors = Vec::new();
146 for hole in &polygon.interiors {
147 let hole_coords = repair_ring(&hole.coords, false, options)?;
148 let hole_ring =
149 LineString::new(hole_coords).map_err(|e| AlgorithmError::GeometryError {
150 message: format!("Failed to create interior ring: {}", e),
151 })?;
152 interiors.push(hole_ring);
153 }
154
155 Polygon::new(exterior, interiors).map_err(|e| AlgorithmError::GeometryError {
156 message: format!("Failed to create repaired polygon: {}", e),
157 })
158}
159
160pub fn repair_linestring(linestring: &LineString) -> Result<LineString> {
174 repair_linestring_with_options(linestring, &RepairOptions::default())
175}
176
177pub fn repair_linestring_with_options(
192 linestring: &LineString,
193 options: &RepairOptions,
194) -> Result<LineString> {
195 let mut coords = linestring.coords.clone();
196
197 if options.remove_duplicates {
198 coords = remove_duplicate_vertices(&coords, options.tolerance);
199 }
200
201 if options.remove_collinear {
202 coords = remove_collinear_vertices(&coords, options.tolerance);
203 }
204
205 if options.remove_spikes {
206 coords = remove_spikes(&coords, options.tolerance);
207 }
208
209 LineString::new(coords).map_err(|e| AlgorithmError::GeometryError {
210 message: format!("Failed to create repaired linestring: {}", e),
211 })
212}
213
214fn repair_ring(
216 coords: &[Coordinate],
217 is_exterior: bool,
218 options: &RepairOptions,
219) -> Result<Vec<Coordinate>> {
220 if coords.is_empty() {
221 return Err(AlgorithmError::EmptyInput {
222 operation: "repair_ring",
223 });
224 }
225
226 let mut result = coords.to_vec();
227
228 if options.close_rings
230 && !coords_equal(
231 &result[0],
232 result
233 .last()
234 .ok_or_else(|| AlgorithmError::InsufficientData {
235 operation: "repair_ring",
236 message: "Ring has no points".to_string(),
237 })?,
238 options.tolerance,
239 )
240 {
241 result.push(result[0]);
242 }
243
244 if options.remove_duplicates {
246 result = remove_duplicate_vertices(&result, options.tolerance);
247 }
248
249 if options.remove_spikes {
251 result = remove_spikes(&result, options.tolerance);
252 }
253
254 if options.remove_collinear {
256 result = remove_collinear_vertices(&result, options.tolerance);
257 }
258
259 if options.fix_orientation {
261 let is_ccw = is_counter_clockwise(&result);
262 if is_exterior && !is_ccw {
263 result.reverse();
264 } else if !is_exterior && is_ccw {
265 result.reverse();
266 }
267 }
268
269 if result.len() < 4 {
271 return Err(AlgorithmError::InsufficientData {
272 operation: "repair_ring",
273 message: format!(
274 "Ring must have at least 4 points after repair, got {}",
275 result.len()
276 ),
277 });
278 }
279
280 Ok(result)
281}
282
283pub fn remove_duplicate_vertices(coords: &[Coordinate], tolerance: f64) -> Vec<Coordinate> {
285 if coords.is_empty() {
286 return Vec::new();
287 }
288
289 let mut result = Vec::with_capacity(coords.len());
290 result.push(coords[0]);
291
292 for i in 1..coords.len() {
293 if !coords_equal_with_tolerance(&coords[i], &coords[i - 1], tolerance) {
294 result.push(coords[i]);
295 }
296 }
297
298 result
299}
300
301pub fn remove_collinear_vertices(coords: &[Coordinate], tolerance: f64) -> Vec<Coordinate> {
303 if coords.len() < 3 {
304 return coords.to_vec();
305 }
306
307 let mut result = Vec::with_capacity(coords.len());
308 result.push(coords[0]);
309
310 for i in 1..coords.len() - 1 {
311 if !are_collinear_with_tolerance(&coords[i - 1], &coords[i], &coords[i + 1], tolerance) {
312 result.push(coords[i]);
313 }
314 }
315
316 result.push(coords[coords.len() - 1]);
317
318 result
319}
320
321pub fn remove_spikes(coords: &[Coordinate], tolerance: f64) -> Vec<Coordinate> {
323 if coords.len() < 3 {
324 return coords.to_vec();
325 }
326
327 let mut result = Vec::with_capacity(coords.len());
328 result.push(coords[0]);
329
330 for i in 1..coords.len() - 1 {
331 if !is_spike(&coords[i - 1], &coords[i], &coords[i + 1], tolerance) {
332 result.push(coords[i]);
333 }
334 }
335
336 result.push(coords[coords.len() - 1]);
337
338 result
339}
340
341pub fn reverse_ring(coords: &[Coordinate]) -> Vec<Coordinate> {
343 let mut result = coords.to_vec();
344 result.reverse();
345 result
346}
347
348pub fn close_ring(coords: &[Coordinate]) -> Result<Vec<Coordinate>> {
350 if coords.is_empty() {
351 return Err(AlgorithmError::EmptyInput {
352 operation: "close_ring",
353 });
354 }
355
356 let mut result = coords.to_vec();
357 if !coords_equal(
358 &result[0],
359 result
360 .last()
361 .ok_or_else(|| AlgorithmError::InsufficientData {
362 operation: "close_ring",
363 message: "Ring has no points".to_string(),
364 })?,
365 f64::EPSILON,
366 ) {
367 result.push(result[0]);
368 }
369
370 Ok(result)
371}
372
373fn coords_equal_with_tolerance(c1: &Coordinate, c2: &Coordinate, tolerance: f64) -> bool {
375 (c1.x - c2.x).abs() < tolerance && (c1.y - c2.y).abs() < tolerance
376}
377
378fn coords_equal(c1: &Coordinate, c2: &Coordinate, tolerance: f64) -> bool {
380 coords_equal_with_tolerance(c1, c2, tolerance)
381}
382
383fn are_collinear_with_tolerance(
385 p1: &Coordinate,
386 p2: &Coordinate,
387 p3: &Coordinate,
388 tolerance: f64,
389) -> bool {
390 let cross = (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y);
391 cross.abs() < tolerance.max(f64::EPSILON)
392}
393
394fn is_spike(prev: &Coordinate, curr: &Coordinate, next: &Coordinate, tolerance: f64) -> bool {
396 let dx1 = curr.x - prev.x;
397 let dy1 = curr.y - prev.y;
398 let dx2 = next.x - curr.x;
399 let dy2 = next.y - curr.y;
400
401 let len1_sq = dx1 * dx1 + dy1 * dy1;
402 let len2_sq = dx2 * dx2 + dy2 * dy2;
403
404 if len1_sq < tolerance || len2_sq < tolerance {
405 return false;
406 }
407
408 let dot = dx1 * dx2 + dy1 * dy2;
409 let len1 = len1_sq.sqrt();
410 let len2 = len2_sq.sqrt();
411
412 let cos_angle = dot / (len1 * len2);
413
414 cos_angle < -0.99
416}
417
418fn is_counter_clockwise(coords: &[Coordinate]) -> bool {
420 let mut area = 0.0;
421 let n = coords.len();
422
423 for i in 0..n {
424 let j = (i + 1) % n;
425 area += coords[i].x * coords[j].y;
426 area -= coords[j].x * coords[i].y;
427 }
428
429 area > 0.0
430}
431
432pub fn fix_self_intersection(polygon: &Polygon) -> Result<Polygon> {
449 repair_polygon(polygon)
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 fn create_square_coords() -> Vec<Coordinate> {
459 vec![
460 Coordinate::new_2d(0.0, 0.0),
461 Coordinate::new_2d(4.0, 0.0),
462 Coordinate::new_2d(4.0, 4.0),
463 Coordinate::new_2d(0.0, 4.0),
464 Coordinate::new_2d(0.0, 0.0),
465 ]
466 }
467
468 #[test]
469 fn test_remove_duplicate_vertices() {
470 let coords = vec![
471 Coordinate::new_2d(0.0, 0.0),
472 Coordinate::new_2d(1.0, 0.0),
473 Coordinate::new_2d(1.0, 0.0), Coordinate::new_2d(2.0, 0.0),
475 ];
476
477 let result = remove_duplicate_vertices(&coords, f64::EPSILON);
478 assert_eq!(result.len(), 3);
479 }
480
481 #[test]
482 fn test_remove_collinear_vertices() {
483 let coords = vec![
484 Coordinate::new_2d(0.0, 0.0),
485 Coordinate::new_2d(1.0, 1.0),
486 Coordinate::new_2d(2.0, 2.0), Coordinate::new_2d(3.0, 0.0),
488 ];
489
490 let result = remove_collinear_vertices(&coords, f64::EPSILON);
491 assert_eq!(result.len(), 3);
492 }
493
494 #[test]
495 fn test_remove_spikes() {
496 let coords = vec![
498 Coordinate::new_2d(0.0, 0.0),
499 Coordinate::new_2d(2.0, 0.0),
500 Coordinate::new_2d(2.001, 0.0), Coordinate::new_2d(0.5, 0.0), Coordinate::new_2d(4.0, 0.0),
503 ];
504
505 let result = remove_spikes(&coords, 0.01);
506 assert!(result.len() >= 2);
509 assert!(result.len() <= coords.len());
510 }
511
512 #[test]
513 fn test_close_ring() {
514 let coords = vec![
515 Coordinate::new_2d(0.0, 0.0),
516 Coordinate::new_2d(4.0, 0.0),
517 Coordinate::new_2d(4.0, 4.0),
518 Coordinate::new_2d(0.0, 4.0),
519 ];
521
522 let result = close_ring(&coords);
523 assert!(result.is_ok());
524
525 if let Ok(closed) = result {
526 assert_eq!(closed.len(), 5);
527 assert!(coords_equal(&closed[0], &closed[4], f64::EPSILON));
528 }
529 }
530
531 #[test]
532 fn test_reverse_ring() {
533 let coords = create_square_coords();
534 let reversed = reverse_ring(&coords);
535
536 assert_eq!(coords.len(), reversed.len());
537 assert!(coords_equal(
538 &coords[0],
539 &reversed[reversed.len() - 1],
540 f64::EPSILON
541 ));
542 }
543
544 #[test]
545 fn test_repair_polygon() {
546 let coords = vec![
548 Coordinate::new_2d(0.0, 0.0),
549 Coordinate::new_2d(4.0, 0.0),
550 Coordinate::new_2d(4.0, 0.0), Coordinate::new_2d(4.0, 4.0),
552 Coordinate::new_2d(0.0, 4.0),
553 Coordinate::new_2d(0.0, 0.0), ];
555
556 let exterior = LineString::new(coords);
557 assert!(exterior.is_ok());
558
559 if let Ok(ext) = exterior {
560 let polygon = Polygon::new(ext, vec![]);
561 if let Ok(poly) = polygon {
565 let result = repair_polygon(&poly);
566 assert!(result.is_ok());
567
568 if let Ok(repaired) = result {
569 assert!(repaired.exterior.coords.len() >= 4);
571 assert!(coords_equal(
573 &repaired.exterior.coords[0],
574 repaired
575 .exterior
576 .coords
577 .last()
578 .ok_or(())
579 .map_err(|_| ())
580 .expect("has last"),
581 f64::EPSILON
582 ));
583 }
584 }
585 }
586 }
587
588 #[test]
589 fn test_repair_linestring() {
590 let coords = vec![
591 Coordinate::new_2d(0.0, 0.0),
592 Coordinate::new_2d(1.0, 0.0),
593 Coordinate::new_2d(1.0, 0.0), Coordinate::new_2d(2.0, 0.0),
595 ];
596
597 let linestring = LineString::new(coords);
598 assert!(linestring.is_ok());
599
600 if let Ok(ls) = linestring {
601 let result = repair_linestring(&ls);
602 assert!(result.is_ok());
603
604 if let Ok(repaired) = result {
605 assert_eq!(repaired.coords.len(), 3);
606 }
607 }
608 }
609
610 #[test]
611 fn test_repair_options() {
612 let options = RepairOptions::default();
613 assert!(options.remove_duplicates);
614 assert!(options.close_rings);
615
616 let all_options = RepairOptions::all();
617 assert!(all_options.remove_collinear);
618
619 let basic_options = RepairOptions::basic();
620 assert!(!basic_options.remove_spikes);
621 }
622
623 #[test]
624 fn test_is_counter_clockwise() {
625 let ccw = vec![
627 Coordinate::new_2d(0.0, 0.0),
628 Coordinate::new_2d(4.0, 0.0),
629 Coordinate::new_2d(4.0, 4.0),
630 Coordinate::new_2d(0.0, 4.0),
631 Coordinate::new_2d(0.0, 0.0),
632 ];
633 assert!(is_counter_clockwise(&ccw));
634
635 let cw = vec![
637 Coordinate::new_2d(0.0, 0.0),
638 Coordinate::new_2d(0.0, 4.0),
639 Coordinate::new_2d(4.0, 4.0),
640 Coordinate::new_2d(4.0, 0.0),
641 Coordinate::new_2d(0.0, 0.0),
642 ];
643 assert!(!is_counter_clockwise(&cw));
644 }
645
646 #[test]
647 fn test_repair_with_custom_options() {
648 let coords = vec![
649 Coordinate::new_2d(0.0, 0.0),
650 Coordinate::new_2d(1.0, 1.0),
651 Coordinate::new_2d(2.0, 2.0), Coordinate::new_2d(3.0, 0.0),
653 ];
654
655 let linestring = LineString::new(coords);
656 assert!(linestring.is_ok());
657
658 if let Ok(ls) = linestring {
659 let options = RepairOptions::default().with_tolerance(1e-6);
660 let result = repair_linestring_with_options(&ls, &options);
661 assert!(result.is_ok());
662 }
663 }
664}