Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ maintenance = { status = "actively-developed" }

[dependencies]
num = "0.2"
derive_builder = "0.7"
serde = { version = "1.0.152", features = ["derive"], optional=true}
serde = { version = "1.0.152", features = ["derive"], optional = true }

[features]
serde = ["dep:serde"]
Expand All @@ -39,5 +38,9 @@ serde = ["dep:serde"]
[dev-dependencies.cargo-husky]
version = "1"
default-features = false # Disable features which are enabled by default
features = ["precommit-hook", "run-cargo-test", "run-cargo-clippy", "run-cargo-fmt"]

features = [
"precommit-hook",
"run-cargo-test",
"run-cargo-clippy",
"run-cargo-fmt",
]
12 changes: 3 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ For documentation, see [docs.rs/quadtree_rs](https://docs.rs/quadtree_rs/).
# Quick Start

```rust
use quadtree_rs::{area::AreaBuilder, point::Point, Quadtree};
use quadtree_rs::{Area, Point, Quadtree};

// Instantiate a new quadtree which associates String values with u64
// coordinates.
Expand All @@ -24,17 +24,11 @@ let mut qt = Quadtree::<u64, String>::new(/*depth=*/4);
assert_eq!(qt.width(), 16);

// Associate the value "foo" with a rectangle of size 2x1, anchored at (0, 0).
let region_a = AreaBuilder::default()
.anchor(Point {x: 0, y: 0})
.dimensions((2, 1))
.build().unwrap();
let region_a = Area::new(2, 1).at(Point {x: 0, y: 0});
qt.insert(region_a, "foo".to_string());

// Query over a region of size 2x2, anchored at (1, 0).
let region_b = AreaBuilder::default()
.anchor(Point {x: 1, y: 0})
.dimensions((2, 2))
.build().unwrap();
let region_b = Area::new(2, 2).at(Point {x: 1, y: 0});
let mut query = qt.query(region_b);

// The query region (region_b) intersects the region "foo" is associated with
Expand Down
16 changes: 5 additions & 11 deletions src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,10 @@
//! A view into a single entry in the Quadtree.
// Influenced by https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html.

use crate::{
area::Area,
point::Point,
};
use crate::{geometry::Area, Point};
use num::PrimInt;
#[cfg(feature = "serde")]
use serde::{
Deserialize,
Serialize,
};
use serde::{Deserialize, Serialize};
use std::default::Default;

/// A region/value association in the [`Quadtree`].
Expand All @@ -37,7 +31,7 @@ use std::default::Default;
/// [`.delete()`]: ../struct.Quadtree.html#method.delete
/// ```
/// use quadtree_rs::{
/// area::Area,
/// Area,
/// Quadtree,
/// };
///
Expand All @@ -55,8 +49,8 @@ use std::default::Default;
///
/// let entry = returned_entries.next().unwrap();
///
/// assert_eq!(entry.anchor().x(), 1);
/// assert_eq!(entry.anchor().y(), 1);
/// assert_eq!(entry.anchor().x, 1);
/// assert_eq!(entry.anchor().y, 1);
/// assert_eq!(entry.width(), 3);
/// assert_eq!(entry.height(), 2);
///
Expand Down
176 changes: 118 additions & 58 deletions src/area.rs → src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! A rectangular region in the tree.

use crate::point;
use num::PrimInt;
#[cfg(feature = "serde")]
use serde::{
Deserialize,
Serialize,
};
use serde::{Deserialize, Serialize};
use std::{
cmp::PartialOrd,
default::Default,
fmt::Debug,
ops::{Add, Sub},
};

/// A rectangular region in 2d space.
Expand All @@ -36,35 +31,15 @@ use std::{
/// quadrant.
/// - The width and height must both be positive and nonzero.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(PartialEq, Eq, Clone, Copy, Hash, Builder)]
#[builder(build_fn(validate = "Self::validate"))]
#[derive(PartialEq, Eq, Clone, Copy, Hash)]
pub struct Area<U>
where
U: PrimInt + Default + PartialOrd,
{
#[builder(setter(into))]
anchor: point::Point<U>,
#[builder(default = "(U::one(), U::one())")]
anchor: Point<U>,
dimensions: (U, U),
}

impl<U> AreaBuilder<U>
where
U: PrimInt + Default + PartialOrd,
{
fn validate(&self) -> Result<(), String> {
if let Some((w, h)) = self.dimensions {
if w <= U::zero() {
return Err("Areas may not have nonpositive widths.".to_string());
}
if h <= U::zero() {
return Err("Areas may not have nonpositive heights.".to_string());
}
}
Ok(())
}
}

impl<U> Debug for Area<U>
where
U: PrimInt + Default + Debug,
Expand All @@ -83,12 +58,9 @@ where
/// Why this custom From<>? Useful for type coercion:
///
/// ```
/// use quadtree_rs::{area::{Area, AreaBuilder}, point::Point};
/// use quadtree_rs::{Area, Point};
///
/// let area: Area<_> = AreaBuilder::default()
/// .anchor(Point{x:1, y:2})
/// .dimensions((3,4))
/// .build().unwrap();
/// let area: Area<_> = Area::new(3,4).at(Point{x:1, y:2});
/// let (anchor, dims) = area.into();
/// assert_eq!(anchor, (1,2));
/// assert_eq!(dims, (3,4));
Expand All @@ -106,8 +78,36 @@ impl<U> Area<U>
where
U: PrimInt + Default,
{
/// Construct a new [`Area`].
/// # Panics
/// Panics if either width or height is negative.
pub fn new(width: U, height: U) -> Self {
assert!(width > U::zero() && height > U::zero());
Self {
anchor: (U::one(), U::one()).into(),
dimensions: (width, height),
}
}

/// Unit area with width and height of one.
pub fn unit() -> Self {
Self::new(U::one(), U::one())
}

/// Returns a new area with it's top-left point set to `anchor`
pub fn at(self, anchor: impl Into<Point<U>>) -> Self {
let Self {
anchor: _,
dimensions,
} = self;
Self {
anchor: anchor.into(),
dimensions,
}
}

/// The top-left coordinate (anchor) of the region.
pub fn anchor(&self) -> point::Point<U> {
pub fn anchor(&self) -> Point<U> {
self.anchor
}

Expand All @@ -123,22 +123,22 @@ where

/// The coordinate of the top edge of the region.
pub fn top_edge(&self) -> U {
self.anchor().y()
self.anchor().y
}

/// The coordinate of the bottom edge of the region.
pub fn bottom_edge(&self) -> U {
self.anchor().y() + self.height()
self.anchor().y + self.height()
}

/// The coordinate of the left edge of the region.
pub fn left_edge(&self) -> U {
self.anchor().x()
self.anchor().x
}

/// The coordinate of the right edge of the region.
pub fn right_edge(&self) -> U {
self.anchor().x() + self.width()
self.anchor().x + self.width()
}

/// Whether or not an area intersects another area.
Expand All @@ -160,21 +160,15 @@ where
}

/// Whether or not an area contains a point.
pub fn contains_pt(self, pt: impl Into<point::Point<U>>) -> bool {
self.contains(
AreaBuilder::default()
.anchor(pt)
.dimensions((U::one(), U::one()))
.build()
.expect("Unexpected error in Area::contains_pt."),
)
pub fn contains_pt(self, pt: impl Into<Point<U>>) -> bool {
self.contains(Self::unit().at(pt))
}

// NB: The center point is an integer and thus rounded, i.e. a 2x2 region at (0,0) has a center
// at (0,0), when in reality the center would be at (0.5, 0.5).
pub(crate) fn center_pt(&self) -> point::Point<U> {
pub(crate) fn center_pt(&self) -> Point<U> {
self.anchor()
+ point::Point {
+ Point {
x: self.width() / Self::two(),
y: self.height() / Self::two(),
}
Expand All @@ -192,24 +186,90 @@ where

impl<P, U> From<(P, (U, U))> for Area<U>
where
P: Into<point::Point<U>>,
P: Into<Point<U>>,
U: PrimInt + Default + PartialOrd,
{
fn from((anchor, dimensions): (P, (U, U))) -> Self {
AreaBuilder::default()
.anchor(anchor)
.dimensions(dimensions)
.build()
.unwrap()
fn from((anchor, (width, height)): (P, (U, U))) -> Self {
Self::new(width, height).at(anchor)
}
}

impl<P, U> From<P> for Area<U>
where
P: Into<point::Point<U>>,
P: Into<Point<U>>,
U: PrimInt + Default + PartialOrd,
{
fn from(anchor: P) -> Self {
AreaBuilder::default().anchor(anchor).build().unwrap()
Self::unit().at(anchor)
}
}

/// A type representing a point in space. Should be passed by value.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(PartialEq, Eq, Clone, Copy, Hash)]
pub struct Point<U> {
pub x: U, // The x-coordinate of the point.
pub y: U, // The y-coordinate of the point.
}

impl<U> Debug for Point<U>
where
U: PrimInt + Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}x{:?}", self.x, self.y)
}
}

impl<U> From<(U, U)> for Point<U>
where
U: PrimInt,
{
fn from((x, y): (U, U)) -> Self {
Self { x, y }
}
}

impl<U> From<&(U, U)> for Point<U>
where
U: PrimInt,
{
fn from((x, y): &(U, U)) -> Self {
Self { x: *x, y: *y }
}
}

impl<U> From<Point<U>> for (U, U)
where
U: PrimInt,
{
fn from(value: Point<U>) -> Self {
(value.x, value.y)
}
}

impl<U> Add for Point<U>
where
U: PrimInt,
{
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
x: self.x.saturating_add(other.x),
y: self.y.saturating_add(other.y),
}
}
}

impl<U> Sub for Point<U>
where
U: PrimInt,
{
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
x: self.x.saturating_sub(other.x),
y: self.y.saturating_sub(other.y),
}
}
}
2 changes: 1 addition & 1 deletion src/handle_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

use crate::{
area::Area,
geometry::Area,
qtinner::QTInner,
traversal::Traversal,
};
Expand Down
2 changes: 1 addition & 1 deletion src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
// limitations under the License.

use crate::{
area::Area,
entry::Entry,
geometry::Area,
handle_iter::HandleIter,
qtinner::QTInner,
traversal::Traversal,
Expand Down
Loading