use crate::error::{SpatialError, SpatialResult};
use scirs2_core::ndarray::{Array1, ArrayView1};
use std::cmp::Ordering;
#[derive(Clone, Debug)]
pub struct Rectangle {
pub(crate) min: Array1<f64>,
pub(crate) max: Array1<f64>,
}
impl Rectangle {
pub fn new(min: Array1<f64>, max: Array1<f64>) -> SpatialResult<Self> {
if min.len() != max.len() {
return Err(SpatialError::DimensionError(format!(
"Min and max must have the same dimensions. Got {} and {}",
min.len(),
max.len()
)));
}
for i in 0..min.len() {
if min[i] > max[i] {
return Err(SpatialError::ValueError(
format!("Min must be less than or equal to max in all dimensions. Dimension {}: {} > {}",
i, min[i], max[i])
));
}
}
Ok(Rectangle { min, max })
}
pub fn from_point(point: &ArrayView1<f64>) -> Self {
Rectangle {
min: point.to_owned(),
max: point.to_owned(),
}
}
pub fn ndim(&self) -> usize {
self.min.len()
}
pub fn area(&self) -> f64 {
let mut area = 1.0;
for i in 0..self.ndim() {
area *= self.max[i] - self.min[i];
}
area
}
pub fn margin(&self) -> f64 {
let mut margin = 0.0;
for i in 0..self.ndim() {
margin += self.max[i] - self.min[i];
}
2.0 * margin
}
pub fn contains_point(&self, point: &ArrayView1<f64>) -> SpatialResult<bool> {
if point.len() != self.ndim() {
return Err(SpatialError::DimensionError(format!(
"Point dimension {} does not match rectangle dimension {}",
point.len(),
self.ndim()
)));
}
for i in 0..self.ndim() {
if point[i] < self.min[i] || point[i] > self.max[i] {
return Ok(false);
}
}
Ok(true)
}
pub fn contains_rectangle(&self, other: &Rectangle) -> SpatialResult<bool> {
if other.ndim() != self.ndim() {
return Err(SpatialError::DimensionError(format!(
"Rectangle dimensions do not match: {} and {}",
self.ndim(),
other.ndim()
)));
}
for i in 0..self.ndim() {
if other.min[i] < self.min[i] || other.max[i] > self.max[i] {
return Ok(false);
}
}
Ok(true)
}
pub fn intersects(&self, other: &Rectangle) -> SpatialResult<bool> {
if other.ndim() != self.ndim() {
return Err(SpatialError::DimensionError(format!(
"Rectangle dimensions do not match: {} and {}",
self.ndim(),
other.ndim()
)));
}
for i in 0..self.ndim() {
if self.max[i] < other.min[i] || self.min[i] > other.max[i] {
return Ok(false);
}
}
Ok(true)
}
pub fn intersection(&self, other: &Rectangle) -> SpatialResult<Rectangle> {
if other.ndim() != self.ndim() {
return Err(SpatialError::DimensionError(format!(
"Rectangle dimensions do not match: {} and {}",
self.ndim(),
other.ndim()
)));
}
let intersects = self.intersects(other)?;
if !intersects {
return Err(SpatialError::ValueError(
"Rectangles do not intersect".into(),
));
}
let mut min = Array1::zeros(self.ndim());
let mut max = Array1::zeros(self.ndim());
for i in 0..self.ndim() {
min[i] = f64::max(self.min[i], other.min[i]);
max[i] = f64::min(self.max[i], other.max[i]);
}
Rectangle::new(min, max)
}
pub fn enlarge(&self, other: &Rectangle) -> SpatialResult<Rectangle> {
if other.ndim() != self.ndim() {
return Err(SpatialError::DimensionError(format!(
"Rectangle dimensions do not match: {} and {}",
self.ndim(),
other.ndim()
)));
}
let mut min = Array1::zeros(self.ndim());
let mut max = Array1::zeros(self.ndim());
for i in 0..self.ndim() {
min[i] = f64::min(self.min[i], other.min[i]);
max[i] = f64::max(self.max[i], other.max[i]);
}
Rectangle::new(min, max)
}
pub fn enlargement_area(&self, other: &Rectangle) -> SpatialResult<f64> {
let enlarged = self.enlarge(other)?;
Ok(enlarged.area() - self.area())
}
pub fn min_distance_to_point(&self, point: &ArrayView1<f64>) -> SpatialResult<f64> {
if point.len() != self.ndim() {
return Err(SpatialError::DimensionError(format!(
"Point dimension {} does not match rectangle dimension {}",
point.len(),
self.ndim()
)));
}
let mut distance_sq = 0.0;
for i in 0..self.ndim() {
if point[i] < self.min[i] {
distance_sq += (point[i] - self.min[i]).powi(2);
} else if point[i] > self.max[i] {
distance_sq += (point[i] - self.max[i]).powi(2);
}
}
Ok(distance_sq.sqrt())
}
pub fn min_distance_to_rectangle(&self, other: &Rectangle) -> SpatialResult<f64> {
if other.ndim() != self.ndim() {
return Err(SpatialError::DimensionError(format!(
"Rectangle dimensions do not match: {} and {}",
self.ndim(),
other.ndim()
)));
}
if self.intersects(other)? {
return Ok(0.0);
}
let mut distance_sq = 0.0;
for i in 0..self.ndim() {
if self.max[i] < other.min[i] {
distance_sq += (other.min[i] - self.max[i]).powi(2);
} else if self.min[i] > other.max[i] {
distance_sq += (self.min[i] - other.max[i]).powi(2);
}
}
Ok(distance_sq.sqrt())
}
}
#[derive(Clone, Debug)]
pub(crate) enum Entry<T>
where
T: Clone,
{
Leaf {
mbr: Rectangle,
data: T,
index: usize,
},
NonLeaf {
mbr: Rectangle,
child: Box<Node<T>>,
},
}
impl<T: Clone> Entry<T> {
pub(crate) fn mbr(&self) -> &Rectangle {
match self {
Entry::Leaf { mbr, .. } => mbr,
Entry::NonLeaf { mbr, .. } => mbr,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct Node<T>
where
T: Clone,
{
pub entries: Vec<Entry<T>>,
pub _isleaf: bool,
pub level: usize,
}
impl<T: Clone> Default for Node<T> {
fn default() -> Self {
Self {
entries: Vec::new(),
_isleaf: true,
level: 0,
}
}
}
impl<T: Clone> Node<T> {
pub fn new(_isleaf: bool, level: usize) -> Self {
Node {
entries: Vec::new(),
_isleaf,
level,
}
}
pub fn size(&self) -> usize {
self.entries.len()
}
pub fn mbr(&self) -> SpatialResult<Option<Rectangle>> {
if self.entries.is_empty() {
return Ok(None);
}
let mut result = self.entries[0].mbr().clone();
for i in 1..self.size() {
result = result.enlarge(self.entries[i].mbr())?;
}
Ok(Some(result))
}
}
#[derive(Clone, Debug)]
pub(crate) struct EntryWithDistance<T>
where
T: Clone,
{
pub entry: Entry<T>,
pub distance: f64,
}
impl<T: Clone> PartialEq for EntryWithDistance<T> {
fn eq(&self, other: &Self) -> bool {
self.distance == other.distance
}
}
impl<T: Clone> Eq for EntryWithDistance<T> {}
impl<T: Clone> PartialOrd for EntryWithDistance<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: Clone> Ord for EntryWithDistance<T> {
fn cmp(&self, other: &Self) -> Ordering {
other
.distance
.partial_cmp(&self.distance)
.unwrap_or(Ordering::Equal)
}
}
#[derive(Clone, Debug)]
pub struct RTree<T>
where
T: Clone,
{
pub(crate) root: Node<T>,
ndim: usize,
pub(crate) min_entries: usize,
pub(crate) maxentries: usize,
size: usize,
height: usize,
}
impl<T: Clone> RTree<T> {
pub fn new(ndim: usize, min_entries: usize, maxentries: usize) -> SpatialResult<Self> {
if ndim == 0 {
return Err(SpatialError::ValueError(
"Number of dimensions must be positive".into(),
));
}
if min_entries < 1 || min_entries > maxentries / 2 {
return Err(SpatialError::ValueError(format!(
"min_entries must be between 1 and maxentries/2, got: {min_entries}"
)));
}
if maxentries < 2 {
return Err(SpatialError::ValueError(format!(
"maxentries must be at least 2, got: {maxentries}"
)));
}
Ok(RTree {
root: Node::new(true, 0),
ndim,
min_entries,
maxentries,
size: 0,
height: 1,
})
}
pub fn ndim(&self) -> usize {
self.ndim
}
pub fn size(&self) -> usize {
self.size
}
pub fn height(&self) -> usize {
self.height
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn clear(&mut self) {
self.root = Node::new(true, 0);
self.size = 0;
self.height = 1;
}
pub(crate) fn increment_size(&mut self) {
self.size += 1;
}
pub(crate) fn decrement_size(&mut self) {
if self.size > 0 {
self.size -= 1;
}
}
pub(crate) fn increment_height(&mut self) {
self.height += 1;
}
#[allow(dead_code)]
pub(crate) fn decrement_height(&mut self) {
if self.height > 0 {
self.height -= 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_rectangle_creation() {
let min = array![0.0, 0.0, 0.0];
let max = array![1.0, 2.0, 3.0];
let rect = Rectangle::new(min.clone(), max.clone()).expect("Operation failed");
assert_eq!(rect.ndim(), 3);
assert_eq!(rect.min, min);
assert_eq!(rect.max, max);
let min_invalid = array![0.0, 3.0, 0.0];
let max_invalid = array![1.0, 2.0, 3.0];
let result = Rectangle::new(min_invalid, max_invalid);
assert!(result.is_err());
let min_dim_mismatch = array![0.0, 0.0];
let max_dim_mismatch = array![1.0, 2.0, 3.0];
let result = Rectangle::new(min_dim_mismatch, max_dim_mismatch);
assert!(result.is_err());
}
#[test]
fn test_rectangle_area_and_margin() {
let min = array![0.0, 0.0, 0.0];
let max = array![1.0, 2.0, 3.0];
let rect = Rectangle::new(min, max).expect("Operation failed");
assert_relative_eq!(rect.area(), 6.0);
assert_relative_eq!(rect.margin(), 12.0);
}
#[test]
fn test_rectangle_contains_point() {
let min = array![0.0, 0.0];
let max = array![1.0, 1.0];
let rect = Rectangle::new(min, max).expect("Operation failed");
let point_inside = array![0.5, 0.5];
assert!(rect
.contains_point(&point_inside.view())
.expect("Operation failed"));
let point_boundary = array![0.0, 0.5];
assert!(rect
.contains_point(&point_boundary.view())
.expect("Operation failed"));
let point_outside = array![2.0, 0.5];
assert!(!rect
.contains_point(&point_outside.view())
.expect("Operation failed"));
let point_dim_mismatch = array![0.5, 0.5, 0.5];
assert!(rect.contains_point(&point_dim_mismatch.view()).is_err());
}
#[test]
fn test_rectangle_intersects() {
let rect1 = Rectangle::new(array![0.0, 0.0], array![2.0, 2.0]).expect("Operation failed");
let rect2 = Rectangle::new(array![1.0, 1.0], array![3.0, 3.0]).expect("Operation failed");
assert!(rect1.intersects(&rect2).expect("Operation failed"));
let rect3 = Rectangle::new(array![2.0, 0.0], array![3.0, 2.0]).expect("Operation failed");
assert!(rect1.intersects(&rect3).expect("Operation failed"));
let rect4 = Rectangle::new(array![3.0, 3.0], array![4.0, 4.0]).expect("Operation failed");
assert!(!rect1.intersects(&rect4).expect("Operation failed"));
let rect5 =
Rectangle::new(array![0.0, 0.0, 0.0], array![1.0, 1.0, 1.0]).expect("Operation failed");
assert!(rect1.intersects(&rect5).is_err());
}
#[test]
fn test_rectangle_enlarge() {
let rect1 = Rectangle::new(array![1.0, 1.0], array![2.0, 2.0]).expect("Operation failed");
let rect2 = Rectangle::new(array![0.0, 0.0], array![3.0, 1.5]).expect("Operation failed");
let enlarged = rect1.enlarge(&rect2).expect("Operation failed");
assert_eq!(enlarged.min, array![0.0, 0.0]);
assert_eq!(enlarged.max, array![3.0, 2.0]);
let enlargement = rect1.enlargement_area(&rect2).expect("Operation failed");
let original_area = rect1.area();
let enlarged_area = enlarged.area();
assert_relative_eq!(enlargement, enlarged_area - original_area);
}
#[test]
fn test_rectangle_distance() {
let rect = Rectangle::new(array![1.0, 1.0], array![3.0, 3.0]).expect("Operation failed");
let point_inside = array![2.0, 2.0];
assert_relative_eq!(
rect.min_distance_to_point(&point_inside.view())
.expect("Operation failed"),
0.0
);
let point_outside = array![0.0, 0.0];
assert_relative_eq!(
rect.min_distance_to_point(&point_outside.view())
.expect("Operation failed"),
2.0_f64.sqrt()
);
let rect2 = Rectangle::new(array![4.0, 4.0], array![5.0, 5.0]).expect("Operation failed");
assert_relative_eq!(
rect.min_distance_to_rectangle(&rect2)
.expect("Operation failed"),
2.0_f64.sqrt()
);
let rect3 = Rectangle::new(array![2.0, 2.0], array![4.0, 4.0]).expect("Operation failed");
assert_relative_eq!(
rect.min_distance_to_rectangle(&rect3)
.expect("Operation failed"),
0.0
);
}
#[test]
fn test_rtree_creation() {
let rtree: RTree<usize> = RTree::new(2, 2, 5).expect("Operation failed");
assert_eq!(rtree.ndim(), 2);
assert_eq!(rtree.size(), 0);
assert_eq!(rtree.height(), 1);
assert!(rtree.is_empty());
assert!(RTree::<usize>::new(0, 2, 5).is_err()); assert!(RTree::<usize>::new(2, 0, 5).is_err()); assert!(RTree::<usize>::new(2, 3, 5).is_err()); assert!(RTree::<usize>::new(2, 2, 1).is_err()); }
}