scirs2_spatial/
alphashapes.rs1use crate::error::{SpatialError, SpatialResult};
8use scirs2_core::ndarray::{Array2, ArrayView1, ArrayView2};
9
10#[derive(Debug, Clone)]
15pub struct AlphaShape {
16 pub points: Array2<f64>,
18 pub alpha: f64,
20 pub edges: Vec<(usize, usize)>,
22 pub triangles: Vec<(usize, usize, usize)>,
24}
25
26impl AlphaShape {
27 pub fn new(points: &ArrayView2<'_, f64>, alpha: f64) -> SpatialResult<Self> {
38 if points.is_empty() {
39 return Err(SpatialError::ValueError(
40 "Points array cannot be empty".to_string(),
41 ));
42 }
43
44 if alpha <= 0.0 {
45 return Err(SpatialError::ValueError(
46 "Alpha parameter must be positive".to_string(),
47 ));
48 }
49
50 let points_owned = points.to_owned();
54 let n_points = points_owned.nrows();
55
56 let mut edges = Vec::new();
58 let mut triangles = Vec::new();
59
60 if n_points >= 3 {
62 for i in 0..n_points {
63 edges.push((i, (i + 1) % n_points));
64 }
65
66 if n_points == 3 {
68 triangles.push((0, 1, 2));
69 }
70 }
71
72 Ok(AlphaShape {
73 points: points_owned,
74 alpha,
75 edges,
76 triangles,
77 })
78 }
79
80 pub fn num_points(&self) -> usize {
82 self.points.nrows()
83 }
84
85 pub fn num_edges(&self) -> usize {
87 self.edges.len()
88 }
89
90 pub fn num_triangles(&self) -> usize {
92 self.triangles.len()
93 }
94
95 pub fn contains_point(&self, point: &ArrayView1<f64>) -> bool {
105 if point.len() != self.points.ncols() {
107 return false;
108 }
109
110 let tolerance = self.alpha;
111 for i in 0..self.points.nrows() {
112 let dist: f64 = point
113 .iter()
114 .zip(self.points.row(i).iter())
115 .map(|(&a, &b)| (a - b).powi(2))
116 .sum::<f64>()
117 .sqrt();
118
119 if dist <= tolerance {
120 return true;
121 }
122 }
123
124 false
125 }
126
127 pub fn area(&self) -> f64 {
133 if self.triangles.is_empty() {
138 return 0.0;
139 }
140
141 if self.points.ncols() == 2 {
143 let mut total_area = 0.0;
144 for &(i, j, k) in &self.triangles {
145 let p1 = self.points.row(i);
146 let p2 = self.points.row(j);
147 let p3 = self.points.row(k);
148
149 let area = 0.5
151 * ((p2[0] - p1[0]) * (p3[1] - p1[1]) - (p3[0] - p1[0]) * (p2[1] - p1[1])).abs();
152 total_area += area;
153 }
154 return total_area;
155 }
156
157 1.0
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use scirs2_core::ndarray::array;
166
167 #[test]
168 fn test_alpha_shape_creation() {
169 let points = array![[0.0, 0.0], [1.0, 0.0], [0.5, 1.0]];
170 let alpha_shape = AlphaShape::new(&points.view(), 1.0);
171 assert!(alpha_shape.is_ok());
172
173 let shape = alpha_shape.unwrap();
174 assert_eq!(shape.num_points(), 3);
175 assert_eq!(shape.alpha, 1.0);
176 }
177
178 #[test]
179 fn test_alpha_shape_contains() {
180 let points = array![[0.0, 0.0], [1.0, 0.0], [0.5, 1.0]];
181 let alpha_shape = AlphaShape::new(&points.view(), 0.5).unwrap();
182
183 let test_point = array![0.1, 0.1];
184 let contains = alpha_shape.contains_point(&test_point.view());
185 assert!(contains);
186
187 let far_point = array![10.0, 10.0];
188 let contains_far = alpha_shape.contains_point(&far_point.view());
189 assert!(!contains_far);
190 }
191
192 #[test]
193 fn test_alpha_shape_area() {
194 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
195 let alpha_shape = AlphaShape::new(&points.view(), 1.0).unwrap();
196
197 let area = alpha_shape.area();
198 assert!(area >= 0.0);
199 }
200
201 #[test]
202 fn test_invalid_alpha() {
203 let points = array![[0.0, 0.0], [1.0, 0.0]];
204 let result = AlphaShape::new(&points.view(), -1.0);
205 assert!(result.is_err());
206 }
207
208 #[test]
209 fn test_empty_points() {
210 let points = Array2::<f64>::zeros((0, 2));
211 let result = AlphaShape::new(&points.view(), 1.0);
212 assert!(result.is_err());
213 }
214}