turned into class

This commit is contained in:
randogoth 2024-03-27 11:08:13 +02:00
parent 29e483be04
commit 1b4a538dff
2 changed files with 218 additions and 173 deletions

View file

@ -14,7 +14,7 @@ DTSCAN was developed by Kim, Jongwon, and Jeongho Cho. "[Delaunay triangulation-
## Functions ## Functions
The combined Xenobalanus implementation is comprised of several key functions: The Xenobalanus class is comprised of several key methods:
- `random_points`: Generates uniformly distributed random points for testing. - `random_points`: Generates uniformly distributed random points for testing.
- `delaunay`: A wrapper of the [Delaunator crate](https://docs.rs/delaunator/latest/delaunator/). Performs Delaunay Triangulation on a given set of points to find their triangular connections. - `delaunay`: A wrapper of the [Delaunator crate](https://docs.rs/delaunator/latest/delaunator/). Performs Delaunay Triangulation on a given set of points to find their triangular connections.
@ -29,31 +29,33 @@ Below is an example code snippet that demonstrates the workflow. This example ge
```rust ```rust
use geo::Point; use geo::Point;
use std::collections::{HashSet}; use std::collections::{HashSet};
use xenobalanus::{delaunay, random_points, preprocess, dtscan, delfin, GeometryData}; use xenobalanus;
fn main() { fn main() {
// Define test area and random points // Define test area and random points
let dots: u32 = 10000; let dots: u32 = 10000;
let side_length: f32 = 10000.0; let side_length: f32 = 10000.0;
let points: Vec<Point<f32>> = random_points((0.0, 0.0), side_length, dots); let mut xeno = Xenobalanus::new();
xeno.random_points((0.0, 0.0), side_length, dots);
println!("Generated {:#?} random dots", dots); println!("Generated {:#?} random dots", dots);
// Run Delaunay triangulation // Run Delaunay triangulation
let triangles_indices: Vec<usize> = delaunay(&points); xeno.delaunay();
println!("Generated Delaunay triangulation"); println!("Generated Delaunay triangulation");
// Pre-process triangles // Pre-process triangles
let geometry_data: GeometryData = preprocess(&points, &triangles_indices, 0); xeno.preprocess(0);
// Execute delfin function with the generated GeometryData // Execute delfin function with the generated GeometryData
let min_area: f32 = 1000.0; // threshold for voidness let min_area: f32 = 1000.0; // threshold for voidness
let min_distance: f32 = 200.0; // threshold for minimum distance let min_distance: f32 = 200.0; // threshold for minimum distance
let void_polygons: Vec<HashSet<usize>> = delfin(&geometry_data, min_area, min_distance); let void_polygons: Vec<HashSet<usize>> = xeno.delfin(min_area, min_distance);
println!("Found {:#?} Voids", void_polygons.len()); println!("Found {:#?} Voids", void_polygons.len());
// Execute DTSCAN with the prepared data // Execute DTSCAN with the prepared data
let min_pts: usize = 5; // threshold for minimum number of points let min_pts: usize = 5; // threshold for minimum number of points
let max_closeness: f32 = 100.5; // threshold for maximum closeness let max_closeness: f32 = 100.5; // threshold for maximum closeness
let clusters: Vec<Vec<usize>> = dtscan(&geometry_data, min_pts, max_closeness); let clusters: Vec<Vec<usize>> = xeno.dtscan(min_pts, max_closeness);
println!("Found {:#?} Attractors", clusters.len()); println!("Found {:#?} Attractors", clusters.len());
} }
```

View file

@ -35,7 +35,6 @@ impl GeometryData {
vertex_connections: HashMap::new(), // Adjusted for DTSCAN vertex_connections: HashMap::new(), // Adjusted for DTSCAN
} }
} }
fn add_triangle(&mut self, index: usize, points: &[Point<f32>], tri_idx: &[usize], types: usize) { fn add_triangle(&mut self, index: usize, points: &[Point<f32>], tri_idx: &[usize], types: usize) {
let point_a: Point<f32> = points[tri_idx[0]]; let point_a: Point<f32> = points[tri_idx[0]];
let point_b: Point<f32> = points[tri_idx[1]]; let point_b: Point<f32> = points[tri_idx[1]];
@ -90,67 +89,110 @@ impl GeometryData {
}); });
} }
} }
} }
fn distance(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 { fn distance(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt() ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
} }
pub fn random_points(center: (f32, f32), side_length: f32, num_points: u32) -> Vec<Point<f32>> { pub struct Xenobalanus {
geometry_data: GeometryData,
points: Vec<Point<f32>>,
triangles: Vec<usize>,
}
impl Xenobalanus {
pub fn new() -> Self {
Xenobalanus {
geometry_data: GeometryData::new(),
points: Vec::new(),
triangles: Vec::new(),
}
}
pub fn points(&self) -> Vec<Vec<f32>> {
self.points.iter()
.map(|point| vec![point.x(), point.y()])
.collect()
}
pub fn points_flat(&self) -> Vec<f32> {
self.points.iter()
.flat_map(|point| vec![point.x(), point.y()])
.collect()
}
pub fn triangles(&self) -> Vec<usize> {
self.triangles.clone()
}
pub fn triangle_vertices(&self) -> Vec<Vec<usize>> {
self.triangles.chunks(3).map(|chunk| {
chunk.iter().map(|&index| index).collect()
}).collect()
}
pub fn triangles_coordinates(&self) -> Vec<Vec<f32>> {
self.triangles.chunks(3).map(|chunk| {
chunk.iter().flat_map(|&index| {
let point = &self.points[index];
vec![point.x(), point.y()]
}).collect()
}).collect()
}
// Additional methods moved into GeometryProcessor, operating on self.geometry_data
pub fn random_points(&mut self, center: (f32, f32), side_length: f32, num_points: u32) {
// generate random points in a square // generate random points in a square
let min_x = center.0 - side_length / 2.0; let min_x = center.0 - side_length / 2.0;
let max_x = center.0 + side_length / 2.0; let max_x = center.0 + side_length / 2.0;
let min_y = center.1 - side_length / 2.0; let min_y = center.1 - side_length / 2.0;
let max_y = center.1 + side_length / 2.0; let max_y = center.1 + side_length / 2.0;
let mut points: Vec<Point<f32>> = Vec::with_capacity(num_points as usize);
let mut rng: rand::prelude::ThreadRng = rand::thread_rng(); let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
for _ in 0..num_points { for _ in 0..num_points {
let x = min_x + rng.gen_range(0.0..=1.0) as f32 * ( max_x - min_x); let x = min_x + rng.gen_range(0.0..=1.0) as f32 * ( max_x - min_x);
let y: f32 = min_y + rng.gen_range(0.0..=1.0) as f32 * ( max_y - min_y); let y: f32 = min_y + rng.gen_range(0.0..=1.0) as f32 * ( max_y - min_y);
points.push(Point::new(x, y)); self.points.push(Point::new(x, y));
}
} }
points pub fn delaunay(&mut self) {
}
pub fn delaunay(points: &Vec<Point<f32>>) -> Vec<usize> {
// Convert geo::Point<f32> to delaunator::Point for triangulation // Convert geo::Point<f32> to delaunator::Point for triangulation
let delaunator_points: Vec<DelaunatorPoint> = points.iter() let delaunator_points: Vec<DelaunatorPoint> = self.points.iter()
.map(|point: &Point<f32>| DelaunatorPoint { x: point.x() as f64, y: point.y() as f64 }) .map(|point: &Point<f32>| DelaunatorPoint { x: point.x() as f64, y: point.y() as f64 })
.collect(); .collect();
// Perform Delaunay triangulation // Perform Delaunay triangulation
let result: delaunator::Triangulation = triangulate(&delaunator_points); let result: delaunator::Triangulation = triangulate(&delaunator_points);
self.triangles = result.triangles
}
// Return the indices of points in the triangles pub fn preprocess(&mut self, types: usize) {
result.triangles
}
pub fn preprocess(points: &[Point<f32>], triangles: &[usize], types: usize) -> GeometryData {
let geometry_data = Arc::new(Mutex::new(GeometryData::new())); let geometry_data = Arc::new(Mutex::new(GeometryData::new()));
triangles.par_chunks(3).enumerate().for_each(|(index, tri_idx)| { self.triangles.par_chunks(3).enumerate().for_each(|(index, tri_idx)| {
let gd = geometry_data.clone(); // Clone Arc for use in each thread let gd = geometry_data.clone(); // Clone Arc for use in each thread
gd.lock().unwrap().add_triangle(index, points, tri_idx, types); gd.lock().unwrap().add_triangle(index, &self.points, tri_idx, types);
}); });
Arc::try_unwrap(geometry_data).unwrap().into_inner().unwrap() self.geometry_data = Arc::try_unwrap(geometry_data).unwrap().into_inner().unwrap()
} }
pub fn delfin( pub fn delfin(
geometry_data: &GeometryData, &self,
min_area: f32, min_area: f32,
min_distance: f32, min_distance: f32,
) -> Vec<HashSet<usize>> { ) -> Vec<HashSet<usize>> {
// Sort all triangles by the longest terminal edge // Sort all triangles by the longest terminal edge
let triangles_sorted: Vec<(usize, f32)> = geometry_data.triangles.iter() let triangles_sorted: Vec<(usize, f32)> = self.geometry_data.triangles.iter()
.filter_map(|triangle_data| { .filter_map(|triangle_data| {
// Only consider triangles with a terminal edge // Only consider triangles with a terminal edge
triangle_data.terminal_edge.map(|terminal_edge| { triangle_data.terminal_edge.map(|terminal_edge| {
// Retrieve the length of the terminal edge if it exists // Retrieve the length of the terminal edge if it exists
geometry_data.edge_lengths.get(&terminal_edge) self.geometry_data.edge_lengths.get(&terminal_edge)
.map(|&length| (triangle_data.index, length)) .map(|&length| (triangle_data.index, length))
}).flatten() }).flatten()
}) })
@ -172,9 +214,9 @@ pub fn delfin(
} }
// Retrieve triangles that share the terminal edge, continue if less than 2 triangles share it // Retrieve triangles that share the terminal edge, continue if less than 2 triangles share it
let triangle_data: &TriangleData = &geometry_data.triangles[triangle_index]; let triangle_data: &TriangleData = &self.geometry_data.triangles[triangle_index];
if let Some(terminal_edge) = triangle_data.terminal_edge { if let Some(terminal_edge) = triangle_data.terminal_edge {
if let Some(connected_triangles) = geometry_data.edge_to_triangles.get(&terminal_edge) { if let Some(connected_triangles) = self.geometry_data.edge_to_triangles.get(&terminal_edge) {
// Proceed only if there are 2 or more triangles sharing the terminal edge // Proceed only if there are 2 or more triangles sharing the terminal edge
if connected_triangles.len() < 2 { if connected_triangles.len() < 2 {
continue; continue;
@ -199,7 +241,7 @@ pub fn delfin(
} }
// Safely access the neighbor triangle's data using its index // Safely access the neighbor triangle's data using its index
if let Some(neighbor_data) = geometry_data.triangles.get(neighbor_idx) { if let Some(neighbor_data) = self.geometry_data.triangles.get(neighbor_idx) {
// Check if the neighbor shares the same terminal edge // Check if the neighbor shares the same terminal edge
// Directly compare the terminal edges as they are both Option<Edge> // Directly compare the terminal edges as they are both Option<Edge>
if neighbor_data.terminal_edge == Some(terminal_edge) { if neighbor_data.terminal_edge == Some(terminal_edge) {
@ -225,7 +267,7 @@ pub fn delfin(
void_polygons.retain(|poly_set: &HashSet<usize>| { void_polygons.retain(|poly_set: &HashSet<usize>| {
// Calculate the total area of the polygon set by summing the areas of the triangles it contains. // Calculate the total area of the polygon set by summing the areas of the triangles it contains.
let total_area: f32 = poly_set.iter() let total_area: f32 = poly_set.iter()
.filter_map(|&idx| geometry_data.triangles.get(idx).and_then(|td| td.area)) .filter_map(|&idx| self.geometry_data.triangles.get(idx).and_then(|td| td.area))
.sum(); .sum();
// Filter based on the area and the minimum number of triangles. // Filter based on the area and the minimum number of triangles.
@ -234,23 +276,23 @@ pub fn delfin(
return void_polygons; return void_polygons;
} }
pub fn dtscan( pub fn dtscan(
geometry_data: &GeometryData, &self,
min_pts: usize, min_pts: usize,
max_closeness: f32, max_closeness: f32,
) -> Vec<Vec<usize>> { ) -> Vec<Vec<usize>> {
let mut clusters: Vec<Vec<usize>> = Vec::new(); let mut clusters: Vec<Vec<usize>> = Vec::new();
let mut visited: HashSet<usize> = HashSet::new(); let mut visited: HashSet<usize> = HashSet::new();
for (&vertex_idx, neighbors) in &geometry_data.vertex_connections { for (&vertex_idx, neighbors) in &self.geometry_data.vertex_connections {
if visited.contains(&vertex_idx) { if visited.contains(&vertex_idx) {
continue; continue;
} }
// Check if vertex is a core vertex based on the number of connections and edge lengths // Check if vertex is a core vertex based on the number of connections and edge lengths
if neighbors.len() >= min_pts && neighbors.iter().all(|&n| { if neighbors.len() >= min_pts && neighbors.iter().all(|&n| {
if let Some(&length) = geometry_data.edge_lengths.get(&Edge(min(vertex_idx, n), max(vertex_idx, n))) { if let Some(&length) = self.geometry_data.edge_lengths.get(&Edge(min(vertex_idx, n), max(vertex_idx, n))) {
length <= max_closeness length <= max_closeness
} else { } else {
false false
@ -267,9 +309,9 @@ pub fn dtscan(
cluster.push(current_vertex); cluster.push(current_vertex);
// Add neighbors that are within max_closeness to to_expand // Add neighbors that are within max_closeness to to_expand
geometry_data.vertex_connections.get(&current_vertex).map(|neighbors: &HashSet<usize>| { self.geometry_data.vertex_connections.get(&current_vertex).map(|neighbors: &HashSet<usize>| {
for &neighbor in neighbors { for &neighbor in neighbors {
if let Some(&length) = geometry_data.edge_lengths.get(&Edge(min(current_vertex, neighbor), max(current_vertex, neighbor))) { if let Some(&length) = self.geometry_data.edge_lengths.get(&Edge(min(current_vertex, neighbor), max(current_vertex, neighbor))) {
if length <= max_closeness && !visited.contains(&neighbor) { if length <= max_closeness && !visited.contains(&neighbor) {
to_expand.push(neighbor); to_expand.push(neighbor);
} }
@ -285,4 +327,5 @@ pub fn dtscan(
} }
clusters clusters
}
} }