Skip to content

Commit

Permalink
move some methods into their own modules, add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
EthanJamesLew committed Dec 31, 2023
1 parent 9f2db42 commit f814683
Show file tree
Hide file tree
Showing 4 changed files with 425 additions and 339 deletions.
42 changes: 42 additions & 0 deletions src/binding.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
/// Python Bindings for Fast Concave Hull Algorithm
use crate::point::Point;

use numpy::{PyArray2, PyReadonlyArray2};
use pyo3::prelude::*;

/// Converts a 2D NumPy array to a vector of `Point` objects.
///
/// Each row of the array should represent a point with 2 columns (x, y coordinates).
/// This function is used to translate Python data structures into Rust equivalents.
///
/// # Arguments
///
/// * `array`: PyReadonlyArray2<f64> - A readonly 2D NumPy array.
///
/// # Returns
///
/// * `PyResult<Vec<Point>>` - A vector of `Point` objects on success, or a Python error on failure.
fn numpy_to_vec_points(array: PyReadonlyArray2<f64>) -> PyResult<Vec<Point>> {
let rows = array.shape()[0];
let columns = array.shape()[1];
Expand Down Expand Up @@ -33,6 +46,22 @@ fn numpy_to_vec_points(array: PyReadonlyArray2<f64>) -> PyResult<Vec<Point>> {
Ok(points)
}

/// Calculates the concave hull of a dataset in 2D.
///
/// This function takes a dataset and a parameter `k`, and computes the concave hull.
/// The `iterate` flag controls the iteration behavior of the algorithm.
///
/// # Arguments
///
/// * `py`: Python<'_> - Python interpreter context.
/// * `dataset`: &PyArray2<f64> - Dataset represented as a 2D NumPy array.
/// * `k`: usize - The number of neighbours to consider for determining the hull smoothness.
/// * `iterate`: bool - Whether to iteratively refine the hull.
///
/// # Returns
///
/// * `PyResult<Py<PyArray2<f64>>>` - A 2D NumPy array representing the concave hull on success,
/// or a Python error on failure.
#[pyfunction]
pub fn concave_hull_2d(
py: Python<'_>,
Expand Down Expand Up @@ -60,6 +89,19 @@ pub fn concave_hull_2d(
Ok(array.into_py(py))
}

/// Initializes the Python module for the concave hull algorithm.
///
/// This function is called when the Python interpreter loads the module.
/// It registers the `Point` class and the `concave_hull_2d` function to the Python module.
///
/// # Arguments
///
/// * `_py`: Python - Python interpreter context.
/// * `m`: &PyModule - The Python module to initialize.
///
/// # Returns
///
/// * `PyResult<()>` - Ok on success, or a Python error on failure.
#[pymodule]
pub fn concave_hull(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Point>()?;
Expand Down
223 changes: 223 additions & 0 deletions src/intersect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/// Geometric Intersection Methods
use crate::point::Point;

/// Determines if two line segments intersect.
///
/// This function checks whether the line segment formed by points `a.0` and `a.1`
/// intersects with the line segment formed by points `b.0` and `b.1`. It uses a
/// geometric approach to calculate the intersection point and checks if this point
/// lies within the bounds of both line segments.
///
/// # Parameters
///
/// * `a`: (&Point, &Point) - A tuple containing two references to `Point` objects,
/// representing the first line segment.
/// * `b`: (&Point, &Point) - A tuple containing two references to `Point` objects,
/// representing the second line segment.
///
/// # Returns
///
/// * `bool` - `true` if the line segments intersect, `false` otherwise.
///
/// # Note
///
/// The function uses a threshold (1E-10) to handle floating-point arithmetic precision issues.
/// This means very close lines that don't technically intersect might be considered as intersecting.
pub fn intersects(a: (&Point, &Point), b: (&Point, &Point)) -> bool {
let ax1 = a.0.x;
let ay1 = a.0.y;
let ax2 = a.1.x;
let ay2 = a.1.y;
let bx1 = b.0.x;
let by1 = b.0.y;
let bx2 = b.1.x;
let by2 = b.1.y;

let a1 = ay2 - ay1;
let b1 = ax1 - ax2;
let c1 = a1 * ax1 + b1 * ay1;
let a2 = by2 - by1;
let b2 = bx1 - bx2;
let c2 = a2 * bx1 + b2 * by1;
let det = a1 * b2 - a2 * b1;

if det.abs() < 1E-10 {
false
} else {
let x = (b2 * c1 - b1 * c2) / det;
let y = (a1 * c2 - a2 * c1) / det;

ax1.min(ax2) <= x
&& (x <= ax1.max(ax2))
&& (ay1.min(ay2) <= y)
&& (y <= ay1.max(ay2))
&& (bx1.min(bx2) <= x)
&& (x <= bx1.max(bx2))
&& (by1.min(by2) <= y)
&& (y <= by1.max(by2))
}
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use super::*;

fn test_intersects() {
let mut values = HashMap::new();
values.insert(
'A',
Point {
x: 0.0,
y: 0.0,
id: 0,
},
);
values.insert(
'B',
Point {
x: -1.5,
y: 3.0,
id: 0,
},
);
values.insert(
'C',
Point {
x: 2.0,
y: 2.0,
id: 0,
},
);
values.insert(
'D',
Point {
x: -2.0,
y: 1.0,
id: 0,
},
);
values.insert(
'E',
Point {
x: -2.5,
y: 5.0,
id: 0,
},
);
values.insert(
'F',
Point {
x: -1.5,
y: 7.0,
id: 0,
},
);
values.insert(
'G',
Point {
x: 1.0,
y: 9.0,
id: 0,
},
);
values.insert(
'H',
Point {
x: -4.0,
y: 7.0,
id: 0,
},
);
values.insert(
'I',
Point {
x: 3.0,
y: 10.0,
id: 0,
},
);
values.insert(
'J',
Point {
x: 2.0,
y: 11.0,
id: 0,
},
);
values.insert(
'K',
Point {
x: -1.0,
y: 11.0,
id: 0,
},
);
values.insert(
'L',
Point {
x: -3.0,
y: 11.0,
id: 0,
},
);
values.insert(
'M',
Point {
x: -5.0,
y: 9.5,
id: 0,
},
);
values.insert(
'N',
Point {
x: -6.0,
y: 7.5,
id: 0,
},
);
values.insert(
'O',
Point {
x: -6.0,
y: 4.0,
id: 0,
},
);
values.insert(
'P',
Point {
x: -5.0,
y: 2.0,
id: 0,
},
);

let test = |a1: char, a2: char, b1: char, b2: char, expected: bool| {
let line1 = (&values[&a1], &values[&a2]);
let line2 = (&values[&b1], &values[&b2]);
assert!(intersects(line1, line2) == expected);
};

test('B', 'D', 'A', 'C', false);
test('A', 'B', 'C', 'D', true);
test('L', 'K', 'H', 'F', false);
test('E', 'C', 'F', 'B', true);
test('P', 'C', 'E', 'B', false);
test('P', 'C', 'A', 'B', true);
test('O', 'E', 'C', 'F', false);
test('L', 'C', 'M', 'N', false);
test('L', 'C', 'N', 'B', false);
test('L', 'C', 'M', 'K', true);
test('L', 'C', 'G', 'I', false);
test('L', 'C', 'I', 'E', true);
test('M', 'O', 'N', 'F', true);
}

#[test]
fn test_intersects_function() {
test_intersects();
}
}
Loading

0 comments on commit f814683

Please sign in to comment.