scirs2_spatial/
polygon.rs

1//! Polygon operations module
2//!
3//! This module provides functionality for working with polygons, including:
4//! - Point in polygon testing
5//! - Area and centroid calculations
6//! - Polygon operations (simplification, transformation)
7//!
8//! # Examples
9//!
10//! ```
11//! use scirs2_core::ndarray::array;
12//! use scirs2_spatial::polygon::point_in_polygon;
13//!
14//! // Create a square polygon
15//! let polygon = array![
16//!     [0.0, 0.0],
17//!     [1.0, 0.0],
18//!     [1.0, 1.0],
19//!     [0.0, 1.0],
20//! ];
21//!
22//! // Test if a point is inside
23//! let inside = point_in_polygon(&[0.5, 0.5], &polygon.view());
24//! assert!(inside);
25//!
26//! // Test if a point is outside
27//! let outside = point_in_polygon(&[1.5, 0.5], &polygon.view());
28//! assert!(!outside);
29//! ```
30
31use num_traits::Float;
32use scirs2_core::ndarray::{Array2, ArrayView2};
33
34/// Tests if a point is inside a polygon using the ray casting algorithm.
35///
36/// The algorithm works by casting a ray from the test point to infinity in the positive x direction,
37/// and counting how many times the ray intersects the polygon boundary. If the count is odd,
38/// the point is inside the polygon; if even, the point is outside.
39///
40/// # Arguments
41///
42/// * `point` - The point to test, as [x, y] coordinates
43/// * `polygon` - The polygon vertices as an array of [x, y] coordinates, assumed to be in order
44///
45/// # Returns
46///
47/// * `true` if the point is inside the polygon, `false` otherwise
48///
49/// # Examples
50///
51/// ```
52/// use scirs2_core::ndarray::array;
53/// use scirs2_spatial::polygon::point_in_polygon;
54///
55/// // Create a square polygon
56/// let polygon = array![
57///     [0.0, 0.0],
58///     [1.0, 0.0],
59///     [1.0, 1.0],
60///     [0.0, 1.0],
61/// ];
62///
63/// // Test if a point is inside
64/// let inside = point_in_polygon(&[0.5, 0.5], &polygon.view());
65/// assert!(inside);
66///
67/// // Test if a point is outside
68/// let outside = point_in_polygon(&[1.5, 0.5], &polygon.view());
69/// assert!(!outside);
70/// ```
71#[allow(dead_code)]
72pub fn point_in_polygon<T: Float>(point: &[T], polygon: &ArrayView2<T>) -> bool {
73    let x = point[0];
74    let y = point[1];
75    let n = polygon.shape()[0];
76
77    if n < 3 {
78        return false; // A polygon must have at least 3 vertices
79    }
80
81    // First check if the point is on the boundary
82    let epsilon = T::from(1e-10).unwrap();
83    if point_on_boundary(point, polygon, epsilon) {
84        return true;
85    }
86
87    // Ray casting algorithm - count intersections of a horizontal ray with polygon edges
88    let mut inside = false;
89
90    for i in 0..n {
91        let j = (i + 1) % n;
92
93        let vi_y = polygon[[i, 1]];
94        let vj_y = polygon[[j, 1]];
95
96        // Check if the edge crosses the horizontal line at y
97        if ((vi_y <= y) && (vj_y > y)) || ((vi_y > y) && (vj_y <= y)) {
98            // Compute x-coordinate of intersection
99            let vi_x = polygon[[i, 0]];
100            let vj_x = polygon[[j, 0]];
101
102            // Check if intersection is to the right of the point
103            let slope = (vj_x - vi_x) / (vj_y - vi_y);
104            let intersect_x = vi_x + (y - vi_y) * slope;
105
106            if x < intersect_x {
107                inside = !inside;
108            }
109        }
110    }
111
112    inside
113}
114
115/// Tests if a point is on the boundary of a polygon.
116///
117/// A point is considered on the boundary if it's within epsilon distance
118/// to any edge of the polygon.
119///
120/// # Arguments
121///
122/// * `point` - The point to test, as [x, y] coordinates
123/// * `polygon` - The polygon vertices as an array of [x, y] coordinates
124/// * `epsilon` - Distance threshold for considering a point on the boundary
125///
126/// # Returns
127///
128/// * `true` if the point is on the polygon boundary, `false` otherwise
129///
130/// # Examples
131///
132/// ```
133/// use scirs2_core::ndarray::array;
134/// use scirs2_spatial::polygon::point_on_boundary;
135///
136/// // Create a square polygon
137/// let polygon = array![
138///     [0.0, 0.0],
139///     [1.0, 0.0],
140///     [1.0, 1.0],
141///     [0.0, 1.0],
142/// ];
143///
144/// // Test if a point is on the boundary
145/// let on_boundary = point_on_boundary(&[0.0, 0.5], &polygon.view(), 1e-10);
146/// assert!(on_boundary);
147///
148/// // Test if a point is not on the boundary
149/// let not_on_boundary = point_on_boundary(&[0.5, 0.5], &polygon.view(), 1e-10);
150/// assert!(!not_on_boundary);
151/// ```
152#[allow(dead_code)]
153pub fn point_on_boundary<T: Float>(point: &[T], polygon: &ArrayView2<T>, epsilon: T) -> bool {
154    let x = point[0];
155    let y = point[1];
156    let n = polygon.shape()[0];
157
158    if n < 2 {
159        return false;
160    }
161
162    for i in 0..n {
163        let j = (i + 1) % n;
164
165        let x1 = polygon[[i, 0]];
166        let y1 = polygon[[i, 1]];
167        let x2 = polygon[[j, 0]];
168        let y2 = polygon[[j, 1]];
169
170        // Calculate distance from point to line segment
171        let length_squared = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
172
173        if length_squared.is_zero() {
174            // The segment is a point
175            let dist = ((x - x1) * (x - x1) + (y - y1) * (y - y1)).sqrt();
176            if dist < epsilon {
177                return true;
178            }
179        } else {
180            // Calculate the projection of the point onto the line
181            let t = ((x - x1) * (x2 - x1) + (y - y1) * (y2 - y1)) / length_squared;
182
183            if t < T::zero() {
184                // Closest point is the start of the segment
185                let dist = ((x - x1) * (x - x1) + (y - y1) * (y - y1)).sqrt();
186                if dist < epsilon {
187                    return true;
188                }
189            } else if t > T::one() {
190                // Closest point is the end of the segment
191                let dist = ((x - x2) * (x - x2) + (y - y2) * (y - y2)).sqrt();
192                if dist < epsilon {
193                    return true;
194                }
195            } else {
196                // Closest point is along the segment
197                let projection_x = x1 + t * (x2 - x1);
198                let projection_y = y1 + t * (y2 - y1);
199                let dist = ((x - projection_x) * (x - projection_x)
200                    + (y - projection_y) * (y - projection_y))
201                    .sqrt();
202                if dist < epsilon {
203                    return true;
204                }
205            }
206        }
207    }
208
209    false
210}
211
212/// Calculate the area of a simple polygon.
213///
214/// Uses the Shoelace formula (also known as the surveyor's formula).
215///
216/// # Arguments
217///
218/// * `polygon` - The polygon vertices as an array of [x, y] coordinates
219///
220/// # Returns
221///
222/// * The area of the polygon
223///
224/// # Examples
225///
226/// ```
227/// use scirs2_core::ndarray::array;
228/// use scirs2_spatial::polygon::polygon_area;
229///
230/// // Create a 1x1 square
231/// let square = array![
232///     [0.0, 0.0],
233///     [1.0, 0.0],
234///     [1.0, 1.0],
235///     [0.0, 1.0],
236/// ];
237///
238/// let area: f64 = polygon_area(&square.view());
239/// assert!((area - 1.0).abs() < 1e-10);
240/// ```
241#[allow(dead_code)]
242pub fn polygon_area<T: Float>(polygon: &ArrayView2<T>) -> T {
243    let n = polygon.shape()[0];
244
245    if n < 3 {
246        return T::zero(); // A polygon must have at least 3 vertices
247    }
248
249    let mut area = T::zero();
250
251    for i in 0..n {
252        let j = (i + 1) % n;
253
254        let xi = polygon[[i, 0]];
255        let yi = polygon[[i, 1]];
256        let xj = polygon[[j, 0]];
257        let yj = polygon[[j, 1]];
258
259        area = area + (xi * yj - xj * yi);
260    }
261
262    (area / (T::one() + T::one())).abs()
263}
264
265/// Calculate the centroid of a simple polygon.
266///
267/// # Arguments
268///
269/// * `polygon` - The polygon vertices as an array of [x, y] coordinates
270///
271/// # Returns
272///
273/// * The centroid coordinates as [x, y]
274///
275/// # Examples
276///
277/// ```
278/// use scirs2_core::ndarray::array;
279/// use scirs2_spatial::polygon::polygon_centroid;
280///
281/// // Create a 1x1 square
282/// let square = array![
283///     [0.0, 0.0],
284///     [1.0, 0.0],
285///     [1.0, 1.0],
286///     [0.0, 1.0],
287/// ];
288///
289/// let centroid: Vec<f64> = polygon_centroid(&square.view());
290/// assert!((centroid[0] - 0.5).abs() < 1e-10);
291/// assert!((centroid[1] - 0.5).abs() < 1e-10);
292/// ```
293#[allow(dead_code)]
294pub fn polygon_centroid<T: Float>(polygon: &ArrayView2<T>) -> Vec<T> {
295    let n = polygon.shape()[0];
296
297    if n < 3 {
298        // Return the average of points for degenerate cases
299        let mut x_sum = T::zero();
300        let mut y_sum = T::zero();
301
302        for i in 0..n {
303            x_sum = x_sum + polygon[[i, 0]];
304            y_sum = y_sum + polygon[[i, 1]];
305        }
306
307        if n > 0 {
308            return vec![x_sum / T::from(n).unwrap(), y_sum / T::from(n).unwrap()];
309        } else {
310            return vec![T::zero(), T::zero()];
311        }
312    }
313
314    let mut cx = T::zero();
315    let mut cy = T::zero();
316    let mut area = T::zero();
317
318    for i in 0..n {
319        let j = (i + 1) % n;
320
321        let xi = polygon[[i, 0]];
322        let yi = polygon[[i, 1]];
323        let xj = polygon[[j, 0]];
324        let yj = polygon[[j, 1]];
325
326        let cross = xi * yj - xj * yi;
327
328        cx = cx + (xi + xj) * cross;
329        cy = cy + (yi + yj) * cross;
330        area = area + cross;
331    }
332
333    area = area / (T::one() + T::one());
334
335    if area.is_zero() {
336        // Degenerate case, return the average of points
337        let mut x_sum = T::zero();
338        let mut y_sum = T::zero();
339
340        for i in 0..n {
341            x_sum = x_sum + polygon[[i, 0]];
342            y_sum = y_sum + polygon[[i, 1]];
343        }
344
345        return vec![x_sum / T::from(n).unwrap(), y_sum / T::from(n).unwrap()];
346    }
347
348    let six = T::from(6).unwrap();
349    cx = cx / (six * area);
350    cy = cy / (six * area);
351
352    // The formula can give negative coordinates if the polygon is
353    // oriented clockwise, so we take the absolute value
354    vec![cx.abs(), cy.abs()]
355}
356
357/// Tests if polygon A contains polygon B (every point of B is inside or on the boundary of A).
358///
359/// # Arguments
360///
361/// * `polygon_a` - The container polygon vertices
362/// * `polygon_b` - The contained polygon vertices
363///
364/// # Returns
365///
366/// * `true` if polygon A contains polygon B, `false` otherwise
367///
368/// # Examples
369///
370/// ```
371/// use scirs2_core::ndarray::array;
372/// use scirs2_spatial::polygon::polygon_contains_polygon;
373///
374/// // Create an outer polygon (large square)
375/// let outer = array![
376///     [0.0, 0.0],
377///     [2.0, 0.0],
378///     [2.0, 2.0],
379///     [0.0, 2.0],
380/// ];
381///
382/// // Create an inner polygon (small square)
383/// let inner = array![
384///     [0.5, 0.5],
385///     [1.5, 0.5],
386///     [1.5, 1.5],
387///     [0.5, 1.5],
388/// ];
389///
390/// let contains = polygon_contains_polygon(&outer.view(), &inner.view());
391/// assert!(contains);
392/// ```
393#[allow(dead_code)]
394pub fn polygon_contains_polygon<T: Float>(
395    polygon_a: &ArrayView2<T>,
396    polygon_b: &ArrayView2<T>,
397) -> bool {
398    // Special case: if A and B are the same polygon, A contains B
399    if polygon_a.shape() == polygon_b.shape() {
400        let n = polygon_a.shape()[0];
401        let mut same = true;
402
403        for i in 0..n {
404            if polygon_a[[i, 0]] != polygon_b[[i, 0]] || polygon_a[[i, 1]] != polygon_b[[i, 1]] {
405                same = false;
406                break;
407            }
408        }
409
410        if same {
411            return true;
412        }
413    }
414
415    let n_b = polygon_b.shape()[0];
416
417    // Check if all vertices of polygon B are inside or on the boundary of polygon A
418    for i in 0..n_b {
419        let point = [polygon_b[[i, 0]], polygon_b[[i, 1]]];
420        if !point_in_polygon(&point, polygon_a) {
421            return false;
422        }
423    }
424
425    // Check for non-coincident edge intersections only if polygons aren't identical
426    if polygon_a.shape() != polygon_b.shape() {
427        let n_a = polygon_a.shape()[0];
428
429        // Check if any edges of B cross any edges of A (that would mean B is not contained)
430        for i in 0..n_a {
431            let j = (i + 1) % n_a;
432            let a1 = [polygon_a[[i, 0]], polygon_a[[i, 1]]];
433            let a2 = [polygon_a[[j, 0]], polygon_a[[j, 1]]];
434
435            for k in 0..n_b {
436                let l = (k + 1) % n_b;
437                let b1 = [polygon_b[[k, 0]], polygon_b[[k, 1]]];
438                let b2 = [polygon_b[[l, 0]], polygon_b[[l, 1]]];
439
440                // Only consider proper intersections (not edge overlaps)
441                if segments_intersect(&a1, &a2, &b1, &b2)
442                    && !segments_overlap(&a1, &a2, &b1, &b2, T::epsilon())
443                    && !point_on_boundary(&a1, polygon_b, T::epsilon())
444                    && !point_on_boundary(&a2, polygon_b, T::epsilon())
445                    && !point_on_boundary(&b1, polygon_a, T::epsilon())
446                    && !point_on_boundary(&b2, polygon_a, T::epsilon())
447                {
448                    return false;
449                }
450            }
451        }
452    }
453
454    true
455}
456
457/// Check if two line segments intersect.
458///
459/// # Arguments
460///
461/// * `a1`, `a2` - The endpoints of the first segment
462/// * `b1`, `b2` - The endpoints of the second segment
463///
464/// # Returns
465///
466/// * `true` if the segments intersect, `false` otherwise
467#[allow(dead_code)]
468fn segments_intersect<T: Float>(a1: &[T], a2: &[T], b1: &[T], b2: &[T]) -> bool {
469    // Function to compute orientation of triplet (p, q, r)
470    // Returns:
471    // 0 -> collinear
472    // 1 -> clockwise
473    // 2 -> counterclockwise
474    let orientation = |p: &[T], q: &[T], r: &[T]| -> i32 {
475        let val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]);
476
477        if val < T::zero() {
478            return 1; // clockwise
479        } else if val > T::zero() {
480            return 2; // counterclockwise
481        }
482
483        0 // collinear
484    };
485
486    // Function to check if point q is on segment pr
487    let on_segment = |p: &[T], q: &[T], r: &[T]| -> bool {
488        q[0] <= p[0].max(r[0])
489            && q[0] >= p[0].min(r[0])
490            && q[1] <= p[1].max(r[1])
491            && q[1] >= p[1].min(r[1])
492    };
493
494    let o1 = orientation(a1, a2, b1);
495    let o2 = orientation(a1, a2, b2);
496    let o3 = orientation(b1, b2, a1);
497    let o4 = orientation(b1, b2, a2);
498
499    // General case
500    if o1 != o2 && o3 != o4 {
501        return true;
502    }
503
504    // Special cases
505    if o1 == 0 && on_segment(a1, b1, a2) {
506        return true;
507    }
508
509    if o2 == 0 && on_segment(a1, b2, a2) {
510        return true;
511    }
512
513    if o3 == 0 && on_segment(b1, a1, b2) {
514        return true;
515    }
516
517    if o4 == 0 && on_segment(b1, a2, b2) {
518        return true;
519    }
520
521    false
522}
523
524/// Check if two line segments overlap (share multiple points).
525///
526/// # Arguments
527///
528/// * `a1`, `a2` - The endpoints of the first segment
529/// * `b1`, `b2` - The endpoints of the second segment
530/// * `epsilon` - Tolerance for floating-point comparisons
531///
532/// # Returns
533///
534/// * `true` if the segments overlap, `false` otherwise
535#[allow(dead_code)]
536fn segments_overlap<T: Float>(a1: &[T], a2: &[T], b1: &[T], b2: &[T], epsilon: T) -> bool {
537    // Check if the segments are collinear
538    let cross = (a2[0] - a1[0]) * (b2[1] - b1[1]) - (a2[1] - a1[1]) * (b2[0] - b1[0]);
539
540    if cross.abs() > epsilon {
541        return false; // Not collinear
542    }
543
544    // Check if the segments overlap on the x-axis
545    let overlap_x = !(a2[0] < b1[0].min(b2[0]) - epsilon || a1[0] > b1[0].max(b2[0]) + epsilon);
546
547    // Check if the segments overlap on the y-axis
548    let overlap_y = !(a2[1] < b1[1].min(b2[1]) - epsilon || a1[1] > b1[1].max(b2[1]) + epsilon);
549
550    overlap_x && overlap_y
551}
552
553/// Check if a polygon is simple (non-self-intersecting).
554///
555/// # Arguments
556///
557/// * `polygon` - The polygon vertices
558///
559/// # Returns
560///
561/// * `true` if the polygon is simple, `false` otherwise
562///
563/// # Examples
564///
565/// ```
566/// use scirs2_core::ndarray::array;
567/// use scirs2_spatial::polygon::is_simple_polygon;
568///
569/// // A simple square
570/// let simple = array![
571///     [0.0, 0.0],
572///     [1.0, 0.0],
573///     [1.0, 1.0],
574///     [0.0, 1.0],
575/// ];
576///
577/// // A self-intersecting "bow tie"
578/// let complex = array![
579///     [0.0, 0.0],
580///     [1.0, 1.0],
581///     [0.0, 1.0],
582///     [1.0, 0.0],
583/// ];
584///
585/// assert!(is_simple_polygon(&simple.view()));
586/// assert!(!is_simple_polygon(&complex.view()));
587/// ```
588#[allow(dead_code)]
589pub fn is_simple_polygon<T: Float>(polygon: &ArrayView2<T>) -> bool {
590    let n = polygon.shape()[0];
591
592    if n < 3 {
593        return true; // Degenerate cases are considered simple
594    }
595
596    // Check each pair of non-adjacent edges for intersection
597    for i in 0..n {
598        let i1 = (i + 1) % n;
599
600        let a1 = [polygon[[i, 0]], polygon[[i, 1]]];
601        let a2 = [polygon[[i1, 0]], polygon[[i1, 1]]];
602
603        for j in i + 2..i + n - 1 {
604            let j_mod = j % n;
605            let j1 = (j_mod + 1) % n;
606
607            // Skip edges that share a vertex
608            if j1 == i || j_mod == i1 {
609                continue;
610            }
611
612            let b1 = [polygon[[j_mod, 0]], polygon[[j_mod, 1]]];
613            let b2 = [polygon[[j1, 0]], polygon[[j1, 1]]];
614
615            if segments_intersect(&a1, &a2, &b1, &b2) {
616                return false; // Self-intersection found
617            }
618        }
619    }
620
621    true
622}
623
624/// Compute the convex hull of a polygon using the Graham scan algorithm.
625///
626/// # Arguments
627///
628/// * `points` - The input points
629///
630/// # Returns
631///
632/// * A new array containing the convex hull vertices
633///
634/// # Examples
635///
636/// ```
637/// use scirs2_core::ndarray::array;
638/// use scirs2_spatial::polygon::convex_hull_graham;
639///
640/// // A set of points
641/// let points = array![
642///     [0.0, 0.0],
643///     [1.0, 0.0],
644///     [0.5, 0.5],  // Inside point
645///     [1.0, 1.0],
646///     [0.0, 1.0],
647/// ];
648///
649/// let hull = convex_hull_graham(&points.view());
650///
651/// // The hull should have only 4 points (the corners)
652/// assert_eq!(hull.shape()[0], 4);
653/// ```
654#[allow(dead_code)]
655pub fn convex_hull_graham<T: Float + std::fmt::Debug>(points: &ArrayView2<T>) -> Array2<T> {
656    let n = points.shape()[0];
657
658    if n <= 3 {
659        // For 3 or fewer points, all points are on the convex hull
660        return points.to_owned();
661    }
662
663    // Find the point with the lowest y-coordinate (and leftmost if tied)
664    let mut lowest = 0;
665    for i in 1..n {
666        if points[[i, 1]] < points[[lowest, 1]]
667            || (points[[i, 1]] == points[[lowest, 1]] && points[[i, 0]] < points[[lowest, 0]])
668        {
669            lowest = i;
670        }
671    }
672
673    // Pivot point
674    let pivot_x = points[[lowest, 0]];
675    let pivot_y = points[[lowest, 1]];
676
677    // Function to compute polar angle of a point relative to the pivot
678    let polar_angle = |x: T, y: T| -> T {
679        let dx = x - pivot_x;
680        let dy = y - pivot_y;
681
682        // Handle special case where points are the same
683        if dx.is_zero() && dy.is_zero() {
684            return T::neg_infinity();
685        }
686
687        // Use atan2 for the polar angle
688        dy.atan2(dx)
689    };
690
691    // Sort points by polar angle
692    let mut indexedpoints: Vec<(usize, T)> = (0..n)
693        .map(|i| (i, polar_angle(points[[i, 0]], points[[i, 1]])))
694        .collect();
695
696    indexedpoints.sort_by(|a, b| {
697        // First by polar angle
698        let angle_cmp = a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal);
699
700        if angle_cmp == std::cmp::Ordering::Equal {
701            // Break ties by distance from pivot
702            let dist_a =
703                (points[[a.0, 0]] - pivot_x).powi(2) + (points[[a.0, 1]] - pivot_y).powi(2);
704            let dist_b =
705                (points[[b.0, 0]] - pivot_x).powi(2) + (points[[b.0, 1]] - pivot_y).powi(2);
706            dist_a
707                .partial_cmp(&dist_b)
708                .unwrap_or(std::cmp::Ordering::Equal)
709        } else {
710            angle_cmp
711        }
712    });
713
714    // Function to check if three points make a right turn
715    let ccw = |i1: usize, i2: usize, i3: usize| -> bool {
716        let p1_x = points[[i1, 0]];
717        let p1_y = points[[i1, 1]];
718        let p2_x = points[[i2, 0]];
719        let p2_y = points[[i2, 1]];
720        let p3_x = points[[i3, 0]];
721        let p3_y = points[[i3, 1]];
722
723        let val = (p2_x - p1_x) * (p3_y - p1_y) - (p2_y - p1_y) * (p3_x - p1_x);
724        val > T::zero()
725    };
726
727    // Graham scan algorithm
728    let mut hull_indices = Vec::new();
729
730    // Add first three points
731    hull_indices.push(lowest);
732
733    for &(index_, _) in &indexedpoints {
734        // Skip pivot point
735        if index_ == lowest {
736            continue;
737        }
738
739        while hull_indices.len() >= 2 {
740            let top = hull_indices.len() - 1;
741            if ccw(hull_indices[top - 1], hull_indices[top], index_) {
742                break;
743            }
744            hull_indices.pop();
745        }
746
747        hull_indices.push(index_);
748    }
749
750    // Create the hull array
751    let mut hull = Array2::zeros((hull_indices.len(), 2));
752    for (i, &idx) in hull_indices.iter().enumerate() {
753        hull[[i, 0]] = points[[idx, 0]];
754        hull[[i, 1]] = points[[idx, 1]];
755    }
756
757    hull
758}
759
760/// Simplify a polygon using the Douglas-Peucker algorithm.
761///
762/// The Douglas-Peucker algorithm recursively removes vertices that contribute less
763/// than a specified tolerance to the shape of the polygon. This is useful for
764/// reducing the complexity of polygons while preserving their essential characteristics.
765///
766/// # Arguments
767///
768/// * `polygon` - The polygon vertices to simplify
769/// * `tolerance` - The perpendicular distance tolerance. Vertices that are closer
770///   than this distance to the line connecting their neighbors may be removed.
771///
772/// # Returns
773///
774/// * A simplified polygon with fewer vertices
775///
776/// # Examples
777///
778/// ```
779/// use scirs2_core::ndarray::array;
780/// use scirs2_spatial::polygon::douglas_peucker_simplify;
781///
782/// // A polygon with many points
783/// let complexpolygon = array![
784///     [0.0, 0.0],
785///     [1.0, 0.1],  // Close to the line from (0,0) to (2,0)
786///     [2.0, 0.0],
787///     [2.0, 2.0],
788///     [0.0, 2.0],
789/// ];
790///
791/// let simplified = douglas_peucker_simplify(&complexpolygon.view(), 0.2);
792///
793/// // Should remove the intermediate point at (1.0, 0.1) since it's close to the line
794/// assert!(simplified.shape()[0] < complexpolygon.shape()[0]);
795/// ```
796#[allow(dead_code)]
797pub fn douglas_peucker_simplify<T: Float + std::fmt::Debug>(
798    polygon: &ArrayView2<T>,
799    tolerance: T,
800) -> Array2<T> {
801    let n = polygon.shape()[0];
802
803    if n <= 2 {
804        return polygon.to_owned();
805    }
806
807    // Create a boolean array to mark which points to keep
808    let mut keep = vec![false; n];
809
810    // Always keep the first and last points
811    keep[0] = true;
812    keep[n - 1] = true;
813
814    // Apply Douglas-Peucker recursively
815    douglas_peucker_recursive(polygon, 0, n - 1, tolerance, &mut keep);
816
817    // Count the points to keep
818    let num_keep = keep.iter().filter(|&&x| x).count();
819
820    // Create the simplified polygon
821    let mut simplified = Array2::zeros((num_keep, 2));
822    let mut simplified_idx = 0;
823
824    for i in 0..n {
825        if keep[i] {
826            simplified[[simplified_idx, 0]] = polygon[[i, 0]];
827            simplified[[simplified_idx, 1]] = polygon[[i, 1]];
828            simplified_idx += 1;
829        }
830    }
831
832    simplified
833}
834
835/// Recursive helper function for Douglas-Peucker algorithm
836#[allow(dead_code)]
837fn douglas_peucker_recursive<T: Float>(
838    polygon: &ArrayView2<T>,
839    start: usize,
840    end: usize,
841    tolerance: T,
842    keep: &mut [bool],
843) {
844    if end <= start + 1 {
845        return;
846    }
847
848    // Find the point with the maximum perpendicular distance from the line
849    let mut max_dist = T::zero();
850    let mut max_idx = start;
851
852    let startpoint = [polygon[[start, 0]], polygon[[start, 1]]];
853    let endpoint = [polygon[[end, 0]], polygon[[end, 1]]];
854
855    for i in start + 1..end {
856        let point = [polygon[[i, 0]], polygon[[i, 1]]];
857        let dist = perpendicular_distance(&point, &startpoint, &endpoint);
858
859        if dist > max_dist {
860            max_dist = dist;
861            max_idx = i;
862        }
863    }
864
865    // If the maximum distance is greater than tolerance, keep the point and recurse
866    if max_dist > tolerance {
867        keep[max_idx] = true;
868        douglas_peucker_recursive(polygon, start, max_idx, tolerance, keep);
869        douglas_peucker_recursive(polygon, max_idx, end, tolerance, keep);
870    }
871}
872
873/// Calculate the perpendicular distance from a point to a line segment
874#[allow(dead_code)]
875fn perpendicular_distance<T: Float>(point: &[T; 2], line_start: &[T; 2], lineend: &[T; 2]) -> T {
876    let dx = lineend[0] - line_start[0];
877    let dy = lineend[1] - line_start[1];
878
879    // If the line segment is actually a point, return distance to that point
880    if dx.is_zero() && dy.is_zero() {
881        let px = point[0] - line_start[0];
882        let py = point[1] - line_start[1];
883        return (px * px + py * py).sqrt();
884    }
885
886    // Calculate the perpendicular distance using the cross product formula
887    let numerator = ((dy * (point[0] - line_start[0])) - (dx * (point[1] - line_start[1]))).abs();
888    let denominator = (dx * dx + dy * dy).sqrt();
889
890    numerator / denominator
891}
892
893/// Simplify a polygon using the Visvalingam-Whyatt algorithm.
894///
895/// The Visvalingam-Whyatt algorithm removes vertices by calculating the area of
896/// triangles formed by consecutive triplets of vertices and removing vertices
897/// that form triangles with areas smaller than a threshold.
898///
899/// # Arguments
900///
901/// * `polygon` - The polygon vertices to simplify
902/// * `min_area` - The minimum triangle area threshold. Vertices forming triangles
903///   with areas smaller than this will be candidates for removal.
904///
905/// # Returns
906///
907/// * A simplified polygon with fewer vertices
908///
909/// # Examples
910///
911/// ```
912/// use scirs2_core::ndarray::array;
913/// use scirs2_spatial::polygon::visvalingam_whyatt_simplify;
914///
915/// // A polygon with some redundant vertices
916/// let polygon = array![
917///     [0.0, 0.0],
918///     [1.0, 0.0],
919///     [1.01, 0.01],  // Forms a very small triangle
920///     [2.0, 0.0],
921///     [2.0, 2.0],
922///     [0.0, 2.0],
923/// ];
924///
925/// let simplified = visvalingam_whyatt_simplify(&polygon.view(), 0.1);
926///
927/// // Should remove vertices that form very small triangles
928/// assert!(simplified.shape()[0] <= polygon.shape()[0]);
929/// ```
930#[allow(dead_code)]
931pub fn visvalingam_whyatt_simplify<T: Float + std::fmt::Debug>(
932    polygon: &ArrayView2<T>,
933    min_area: T,
934) -> Array2<T> {
935    let n = polygon.shape()[0];
936
937    if n <= 3 {
938        return polygon.to_owned();
939    }
940
941    // Create a list of vertices with their effective areas
942    let mut vertices: Vec<(usize, T)> = Vec::new();
943    let mut active = vec![true; n];
944
945    // Calculate initial areas for all vertices
946    for i in 0..n {
947        let area = calculate_triangle_area(polygon, i, &active);
948        vertices.push((i, area));
949    }
950
951    // Sort by _area (smallest first)
952    vertices.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
953
954    // Remove vertices with areas smaller than threshold
955    let removal_candidates: Vec<usize> = vertices
956        .iter()
957        .filter(|(_, area)| *area < min_area)
958        .map(|(idx_, _)| *idx_)
959        .collect();
960
961    for vertex_idx in removal_candidates {
962        if count_active(&active) > 3 && active[vertex_idx] {
963            active[vertex_idx] = false;
964
965            // Recalculate areas for neighboring vertices
966            let prev = find_previous_active(vertex_idx, &active, n);
967            let next = find_next_active(vertex_idx, &active, n);
968
969            if let (Some(prev_idx), Some(next_idx)) = (prev, next) {
970                // Update the _area for the previous vertex
971                update_vertex_area(polygon, prev_idx, &active, &mut vertices);
972                // Update the _area for the next vertex
973                update_vertex_area(polygon, next_idx, &active, &mut vertices);
974            }
975        }
976    }
977
978    // Create the simplified polygon
979    let num_active = count_active(&active);
980    let mut simplified = Array2::zeros((num_active, 2));
981    let mut simplified_idx = 0;
982
983    for i in 0..n {
984        if active[i] {
985            simplified[[simplified_idx, 0]] = polygon[[i, 0]];
986            simplified[[simplified_idx, 1]] = polygon[[i, 1]];
987            simplified_idx += 1;
988        }
989    }
990
991    simplified
992}
993
994/// Calculate the area of triangle formed by vertex i and its active neighbors
995#[allow(dead_code)]
996fn calculate_triangle_area<T: Float>(
997    polygon: &ArrayView2<T>,
998    vertex_idx: usize,
999    active: &[bool],
1000) -> T {
1001    let n = polygon.shape()[0];
1002
1003    let prev = find_previous_active(vertex_idx, active, n);
1004    let next = find_next_active(vertex_idx, active, n);
1005
1006    match (prev, next) {
1007        (Some(prev_idx), Some(next_idx)) => {
1008            let p1 = [polygon[[prev_idx, 0]], polygon[[prev_idx, 1]]];
1009            let p2 = [polygon[[vertex_idx, 0]], polygon[[vertex_idx, 1]]];
1010            let p3 = [polygon[[next_idx, 0]], polygon[[next_idx, 1]]];
1011
1012            triangle_area(&p1, &p2, &p3)
1013        }
1014        _ => T::infinity(), // End vertices have infinite area (never removed)
1015    }
1016}
1017
1018/// Calculate the area of a triangle given three points
1019#[allow(dead_code)]
1020fn triangle_area<T: Float>(p1: &[T; 2], p2: &[T; 2], p3: &[T; 2]) -> T {
1021    ((p1[0] * (p2[1] - p3[1]) + p2[0] * (p3[1] - p1[1]) + p3[0] * (p1[1] - p2[1]))
1022        / (T::one() + T::one()))
1023    .abs()
1024}
1025
1026/// Find the previous active vertex (wrapping around)
1027#[allow(dead_code)]
1028fn find_previous_active(current: usize, active: &[bool], n: usize) -> Option<usize> {
1029    for i in 1..n {
1030        let idx = (current + n - i) % n;
1031        if active[idx] && idx != current {
1032            return Some(idx);
1033        }
1034    }
1035    None
1036}
1037
1038/// Find the next active vertex (wrapping around)
1039#[allow(dead_code)]
1040fn find_next_active(current: usize, active: &[bool], n: usize) -> Option<usize> {
1041    for i in 1..n {
1042        let idx = (current + i) % n;
1043        if active[idx] && idx != current {
1044            return Some(idx);
1045        }
1046    }
1047    None
1048}
1049
1050/// Count the number of active vertices
1051#[allow(dead_code)]
1052fn count_active(active: &[bool]) -> usize {
1053    active.iter().filter(|&&x| x).count()
1054}
1055
1056/// Update the area for a specific vertex in the vertices list
1057#[allow(dead_code)]
1058fn update_vertex_area<T: Float + std::fmt::Debug>(
1059    polygon: &ArrayView2<T>,
1060    vertex_idx: usize,
1061    active: &[bool],
1062    vertices: &mut [(usize, T)],
1063) {
1064    let new_area = calculate_triangle_area(polygon, vertex_idx, active);
1065
1066    // Find and update the vertex in the list
1067    for (_idx, area) in vertices.iter_mut() {
1068        if *_idx == vertex_idx {
1069            *area = new_area;
1070            break;
1071        }
1072    }
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077    use super::*;
1078    use approx::assert_relative_eq;
1079    use scirs2_core::ndarray::array;
1080
1081    #[test]
1082    fn testpoint_in_polygon() {
1083        // Simple square
1084        let square = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],];
1085
1086        // Points inside
1087        assert!(point_in_polygon(&[0.5, 0.5], &square.view()));
1088        assert!(point_in_polygon(&[0.1, 0.1], &square.view()));
1089        assert!(point_in_polygon(&[0.9, 0.9], &square.view()));
1090
1091        // Points outside
1092        assert!(!point_in_polygon(&[1.5, 0.5], &square.view()));
1093        assert!(!point_in_polygon(&[-0.5, 0.5], &square.view()));
1094        assert!(!point_in_polygon(&[0.5, 1.5], &square.view()));
1095        assert!(!point_in_polygon(&[0.5, -0.5], &square.view()));
1096
1097        // Points on boundary (considered inside)
1098        assert!(point_in_polygon(&[0.0, 0.5], &square.view()));
1099        assert!(point_in_polygon(&[1.0, 0.5], &square.view()));
1100        assert!(point_in_polygon(&[0.5, 0.0], &square.view()));
1101        assert!(point_in_polygon(&[0.5, 1.0], &square.view()));
1102
1103        // More complex polygon (concave)
1104        let concave = array![[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [1.0, 1.0], [0.0, 2.0],];
1105
1106        // Inside the concave part - this point is actually outside the polygon
1107        // Modified test to assert the correct result
1108        assert!(!point_in_polygon(&[1.0, 1.5], &concave.view()));
1109
1110        // Inside the convex part
1111        assert!(point_in_polygon(&[0.5, 0.5], &concave.view()));
1112
1113        // Outside (but inside the bounding box)
1114        // For the given concave shape, point (1.5, 0.5) is actually inside
1115        assert!(point_in_polygon(&[1.5, 0.5], &concave.view()));
1116    }
1117
1118    #[test]
1119    fn testpoint_on_boundary() {
1120        // Simple square
1121        let square = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],];
1122
1123        let epsilon = 1e-10;
1124
1125        // Points on boundary
1126        assert!(point_on_boundary(&[0.0, 0.5], &square.view(), epsilon));
1127        assert!(point_on_boundary(&[1.0, 0.5], &square.view(), epsilon));
1128        assert!(point_on_boundary(&[0.5, 0.0], &square.view(), epsilon));
1129        assert!(point_on_boundary(&[0.5, 1.0], &square.view(), epsilon));
1130
1131        // Corner points
1132        assert!(point_on_boundary(&[0.0, 0.0], &square.view(), epsilon));
1133        assert!(point_on_boundary(&[1.0, 0.0], &square.view(), epsilon));
1134        assert!(point_on_boundary(&[1.0, 1.0], &square.view(), epsilon));
1135        assert!(point_on_boundary(&[0.0, 1.0], &square.view(), epsilon));
1136
1137        // Points not on boundary
1138        assert!(!point_on_boundary(&[0.5, 0.5], &square.view(), epsilon));
1139        assert!(!point_on_boundary(&[1.5, 0.5], &square.view(), epsilon));
1140        assert!(!point_on_boundary(&[0.0, 2.0], &square.view(), epsilon));
1141
1142        // Points near but not exactly on boundary should still be detected
1143        // with a larger epsilon
1144        assert!(point_on_boundary(&[0.0, 0.5 + 1e-5], &square.view(), 1e-4));
1145    }
1146
1147    #[test]
1148    fn testpolygon_area() {
1149        // Square with area 1
1150        let square = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],];
1151
1152        assert_relative_eq!(polygon_area(&square.view()), 1.0, epsilon = 1e-10);
1153
1154        // Rectangle with area 6
1155        let rectangle = array![[0.0, 0.0], [3.0, 0.0], [3.0, 2.0], [0.0, 2.0],];
1156
1157        assert_relative_eq!(polygon_area(&rectangle.view()), 6.0, epsilon = 1e-10);
1158
1159        // Triangle with area 0.5
1160        let triangle = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0],];
1161
1162        assert_relative_eq!(polygon_area(&triangle.view()), 0.5, epsilon = 1e-10);
1163
1164        // L-shaped polygon
1165        let lshape = array![
1166            [0.0, 0.0],
1167            [2.0, 0.0],
1168            [2.0, 1.0],
1169            [1.0, 1.0],
1170            [1.0, 2.0],
1171            [0.0, 2.0],
1172        ];
1173
1174        assert_relative_eq!(polygon_area(&lshape.view()), 3.0, epsilon = 1e-10);
1175    }
1176
1177    #[test]
1178    fn testpolygon_centroid() {
1179        // Square with centroid at (0.5, 0.5)
1180        let square = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],];
1181
1182        let centroid = polygon_centroid(&square.view());
1183        assert_relative_eq!(centroid[0], 0.5, epsilon = 1e-10);
1184        assert_relative_eq!(centroid[1], 0.5, epsilon = 1e-10);
1185
1186        // Rectangle with centroid at (1.5, 1.0)
1187        let rectangle = array![[0.0, 0.0], [3.0, 0.0], [3.0, 2.0], [0.0, 2.0],];
1188
1189        let centroid = polygon_centroid(&rectangle.view());
1190        assert_relative_eq!(centroid[0], 1.5, epsilon = 1e-10);
1191        assert_relative_eq!(centroid[1], 1.0, epsilon = 1e-10);
1192
1193        // Triangle with centroid at (1/3, 1/3)
1194        let triangle = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0],];
1195
1196        let centroid = polygon_centroid(&triangle.view());
1197        assert_relative_eq!(centroid[0], 1.0 / 3.0, epsilon = 1e-10);
1198        assert_relative_eq!(centroid[1], 1.0 / 3.0, epsilon = 1e-10);
1199    }
1200
1201    #[test]
1202    fn testpolygon_contains_polygon() {
1203        // Outer square
1204        let outer = array![[0.0, 0.0], [3.0, 0.0], [3.0, 3.0], [0.0, 3.0],];
1205
1206        // Inner square
1207        let inner = array![[1.0, 1.0], [2.0, 1.0], [2.0, 2.0], [1.0, 2.0],];
1208
1209        // Outer should contain inner
1210        assert!(polygon_contains_polygon(&outer.view(), &inner.view()));
1211
1212        // Inner should not contain outer
1213        assert!(!polygon_contains_polygon(&inner.view(), &outer.view()));
1214
1215        // Overlapping squares
1216        let overlap = array![[2.0, 2.0], [4.0, 2.0], [4.0, 4.0], [2.0, 4.0],];
1217
1218        // Neither should fully contain the other
1219        assert!(!polygon_contains_polygon(&outer.view(), &overlap.view()));
1220        assert!(!polygon_contains_polygon(&overlap.view(), &outer.view()));
1221
1222        // A polygon contains itself
1223        assert!(polygon_contains_polygon(&outer.view(), &outer.view()));
1224    }
1225
1226    #[test]
1227    fn test_is_simple_polygon() {
1228        // Simple square (non-self-intersecting)
1229        let square = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],];
1230
1231        assert!(is_simple_polygon(&square.view()));
1232
1233        // Self-intersecting bow tie
1234        let bowtie = array![[0.0, 0.0], [1.0, 1.0], [0.0, 1.0], [1.0, 0.0],];
1235
1236        assert!(!is_simple_polygon(&bowtie.view()));
1237
1238        // More complex non-self-intersecting polygon
1239        let complex = array![[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [1.0, 1.0], [0.0, 2.0],];
1240
1241        assert!(is_simple_polygon(&complex.view()));
1242    }
1243
1244    #[test]
1245    fn test_convex_hull_graham() {
1246        // Simple test with a square and a point inside
1247        let points = array![
1248            [0.0, 0.0],
1249            [1.0, 0.0],
1250            [0.5, 0.5], // This point should not be in the hull
1251            [1.0, 1.0],
1252            [0.0, 1.0],
1253        ];
1254
1255        let hull = convex_hull_graham(&points.view());
1256
1257        // The hull should have only 4 points (the corners)
1258        assert_eq!(hull.shape()[0], 4);
1259
1260        // Check that the inside point is not in the hull
1261        let mut found_inside = false;
1262        for i in 0..hull.shape()[0] {
1263            if (hull[[i, 0]] - 0.5).abs() < 1e-10 && (hull[[i, 1]] - 0.5).abs() < 1e-10 {
1264                found_inside = true;
1265                break;
1266            }
1267        }
1268        assert!(!found_inside);
1269
1270        // Test with points in a circle-like arrangement
1271        let mut circlepoints = Array2::zeros((8, 2));
1272        for i in 0..8 {
1273            let angle = 2.0 * std::f64::consts::PI * (i as f64) / 8.0;
1274            circlepoints[[i, 0]] = angle.cos();
1275            circlepoints[[i, 1]] = angle.sin();
1276        }
1277
1278        // Add some interior points
1279        let allpoints = array![
1280            [0.0, 0.0],  // Center point
1281            [0.5, 0.0],  // Interior point
1282            [0.0, 0.5],  // Interior point
1283            [-0.5, 0.0], // Interior point
1284            [0.0, -0.5], // Interior point
1285            // Add the circle points
1286            [1.0, 0.0],
1287            [
1288                std::f64::consts::FRAC_1_SQRT_2,
1289                std::f64::consts::FRAC_1_SQRT_2
1290            ],
1291            [0.0, 1.0],
1292            [
1293                -std::f64::consts::FRAC_1_SQRT_2,
1294                std::f64::consts::FRAC_1_SQRT_2
1295            ],
1296            [-1.0, 0.0],
1297            [
1298                -std::f64::consts::FRAC_1_SQRT_2,
1299                -std::f64::consts::FRAC_1_SQRT_2
1300            ],
1301            [0.0, -1.0],
1302            [
1303                std::f64::consts::FRAC_1_SQRT_2,
1304                -std::f64::consts::FRAC_1_SQRT_2
1305            ],
1306        ];
1307
1308        let hull = convex_hull_graham(&allpoints.view());
1309
1310        // The hull should have 8 points (the circle points)
1311        assert_eq!(hull.shape()[0], 8);
1312    }
1313}