Skip to content

Commit

Permalink
Merge #74
Browse files Browse the repository at this point in the history
74: generic geom + data container r=urschrei a=urschrei

- [x ] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/master/CODE_OF_CONDUCT.md).
- [x] I added an entry to `rstar/CHANGELOG.md` if knowledge of this change could be valuable to users.
---

This is a generic implementation of `PointWithData` and #73. <s>All fields are private and require getters, and</s> The geometry field can't be modified after creation in order to prevent a logic error if it's in an RTree. This PR also adds a deprecation notice to `PointWithData`.

For discussion:

- ~~The name~~
- ~~struct fields~~
- ~~Use of getters~~
- ~~What should we be deriving?~~
- ~~Can we derive serde traits?~~
- ~~Is there any need for a `Default` on either field?~~

Co-authored-by: Stephan Hügel <shugel@tcd.ie>
  • Loading branch information
bors[bot] and urschrei authored Aug 5, 2021
2 parents 14f019f + 1562839 commit 4310253
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 1 deletion.
5 changes: 5 additions & 0 deletions rstar/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Unreleased

## Added
- A generic container for a geometry and associated data: `GeomWithData` ([PR](https://github.com/georust/rstar/pull/74))

# 0.9.0

## Added
Expand Down
2 changes: 1 addition & 1 deletion rstar/src/point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl<S> RTreeNum for S where S: Bounded + Num + Clone + Copy + Signed + PartialO
///
/// This trait should be used for interoperability with other point types, not to define custom objects
/// that can be inserted into r-trees. Use [`crate::RTreeObject`] or
/// [`crate::primitives::PointWithData`] instead.
/// [`crate::primitives::GeomWithData`] instead.
/// This trait defines points, not points with metadata.
///
/// `Point` is implemented out of the box for arrays like `[f32; 2]` or `[f64; 7]` (up to dimension 9).
Expand Down
136 changes: 136 additions & 0 deletions rstar/src/primitives/geom_with_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use crate::envelope::Envelope;
use crate::object::PointDistance;
use crate::{object::RTreeObject, point::Point};

/// An [RTreeObject] with a geometry and some associated data that can be inserted into an r-tree.
///
/// Often, adding metadata (like a database ID) to a geometry is required before adding it
/// into an r-tree. This struct removes some of the boilerplate required to do so.
///
/// **Note:** while the container itself implements [RTreeObject], you will have to go through its
/// [`geom`](GeomWithData::geom) method in order to access geometry-specific methods.
///
/// # Example
/// ```
/// use rstar::{RTree, PointDistance};
/// use rstar::primitives::GeomWithData;
///
/// type RestaurantLocation = GeomWithData<[f64; 2], &'static str>;
///
/// let mut restaurants = RTree::new();
/// restaurants.insert(RestaurantLocation::new([0.3, 0.2], "Pete's Pizza Place"));
/// restaurants.insert(RestaurantLocation::new([-0.8, 0.0], "The Great Steak"));
/// restaurants.insert(RestaurantLocation::new([0.2, -0.2], "Fishy Fortune"));
///
/// let my_location = [0.0, 0.0];
///
/// // Now find the closest restaurant!
/// let place = restaurants.nearest_neighbor(&my_location).unwrap();
/// println!("Let's go to {}", place.data);
/// println!("It's really close, only {} miles", place.distance_2(&my_location));
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GeomWithData<R: RTreeObject, T> {
geom: R,
/// Data to be associated with the geometry being stored in the [`RTree`](crate::RTree).
pub data: T,
}

impl<R: RTreeObject, T> RTreeObject for GeomWithData<R, T> {
type Envelope = R::Envelope;

fn envelope(&self) -> Self::Envelope {
self.geom.envelope()
}
}

impl<R: PointDistance, T> PointDistance for GeomWithData<R, T> {
fn distance_2(
&self,
point: &<Self::Envelope as Envelope>::Point,
) -> <<Self::Envelope as Envelope>::Point as Point>::Scalar {
self.geom.distance_2(point)
}

fn contains_point(&self, p: &<Self::Envelope as Envelope>::Point) -> bool {
self.geom.contains_point(p)
}

fn distance_2_if_less_or_equal(
&self,
point: &<Self::Envelope as Envelope>::Point,
max_distance_2: <<Self::Envelope as Envelope>::Point as Point>::Scalar,
) -> Option<<<Self::Envelope as Envelope>::Point as Point>::Scalar> {
self.geom.distance_2_if_less_or_equal(point, max_distance_2)
}
}

impl<R: RTreeObject, T> GeomWithData<R, T> {
/// Create a new [GeomWithData] struct using the provided geometry and data.
pub fn new(geom: R, data: T) -> Self {
Self { geom, data }
}

/// Get a reference to the container's geometry.
pub fn geom(&self) -> &R {
&self.geom
}
}

#[cfg(test)]
mod test {
use super::GeomWithData;
use crate::object::PointDistance;

use approx::*;

use crate::{primitives::Line, RTree};

#[test]
fn container_in_rtree() {
let line_1 = GeomWithData::new(Line::new([0.0, 0.0], [1.0, 1.0]), ());
let line_2 = GeomWithData::new(Line::new([0.0, 0.0], [-1.0, 1.0]), ());
let tree = RTree::bulk_load(vec![line_1, line_2]);

assert!(tree.contains(&line_1));
}

#[test]
fn container_edge_distance() {
let edge = GeomWithData::new(Line::new([0.5, 0.5], [0.5, 2.0]), 1usize);

assert_abs_diff_eq!(edge.distance_2(&[0.5, 0.5]), 0.0);
assert_abs_diff_eq!(edge.distance_2(&[0.0, 0.5]), 0.5 * 0.5);
assert_abs_diff_eq!(edge.distance_2(&[0.5, 1.0]), 0.0);
assert_abs_diff_eq!(edge.distance_2(&[0.0, 0.0]), 0.5);
assert_abs_diff_eq!(edge.distance_2(&[0.0, 1.0]), 0.5 * 0.5);
assert_abs_diff_eq!(edge.distance_2(&[1.0, 1.0]), 0.5 * 0.5);
assert_abs_diff_eq!(edge.distance_2(&[1.0, 3.0]), 0.5 * 0.5 + 1.0);
}

#[test]
fn container_length_2() {
let line = GeomWithData::new(Line::new([1, -1], [5, 5]), 1usize);

assert_eq!(line.geom().length_2(), 16 + 36);
}

#[test]
fn container_nearest_neighbour() {
let mut lines = RTree::new();
lines.insert(GeomWithData::new(
Line::new([0.0, 0.0], [1.0, 1.0]),
"Line A",
));
lines.insert(GeomWithData::new(
Line::new([0.0, 0.0], [-1.0, 1.0]),
"Line B",
));
let my_location = [0.0, 0.0];
// Now find the closest line
let place = lines.nearest_neighbor(&my_location).unwrap();

assert_eq!(place.data, "Line A");
}
}
2 changes: 2 additions & 0 deletions rstar/src/primitives/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Contains primitives ready for insertion into an r-tree.

mod geom_with_data;
mod line;
mod point_with_data;
mod rectangle;

pub use self::geom_with_data::GeomWithData;
pub use self::line::Line;
pub use self::point_with_data::PointWithData;
pub use self::rectangle::Rectangle;
3 changes: 3 additions & 0 deletions rstar/src/primitives/point_with_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::{Point, PointDistance, RTreeObject, AABB};

/// A point with some associated data that can be inserted into an r-tree.
///
/// **Note**: `PointWithData` has been deprecated in favour of [`GeomWithData`](crate::primitives::GeomWithData)
///
/// Often, adding metadata (like a database index) to a point is required before adding them
/// into an r-tree. This struct removes some of the boilerplate required to do so.
///
Expand Down Expand Up @@ -34,6 +36,7 @@ pub struct PointWithData<T, P> {

impl<T, P> PointWithData<T, P> {
/// Creates a new `PointWithData` with the provided data.
#[deprecated(note = "`PointWithData` is deprecated. Please switch to `GeomWithData`")]
pub fn new(data: T, point: P) -> Self {
PointWithData { data, point }
}
Expand Down

0 comments on commit 4310253

Please sign in to comment.