Skip to content
Merged
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
2 changes: 2 additions & 0 deletions geo/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
changing some trait constraints, but they are unlikely to affect you in
practice unless you have your own Relate implementation.
- <https://github.com/georust/geo/pull/1317>
- Add: PreparedGeometry::geometry and into_geometry to get at the inner geometry type.
- <https://github.com/georust/geo/pull/1318>

## 0.29.3 - 2024.12.03

Expand Down
1 change: 1 addition & 0 deletions geo/src/algorithm/relate/geomgraph/index/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod segment_intersector;
mod simple_edge_set_intersector;

pub(crate) use edge_set_intersector::EdgeSetIntersector;
pub(crate) use prepared_geometry::prepare_geometry;
pub use prepared_geometry::PreparedGeometry;
pub(crate) use rstar_edge_set_intersector::RStarEdgeSetIntersector;
pub(crate) use segment::Segment;
Expand Down
219 changes: 63 additions & 156 deletions geo/src/algorithm/relate/geomgraph/index/prepared_geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::cell::RefCell;
use std::rc::Rc;

use crate::dimensions::Dimensions;
use rstar::{RTree, RTreeNum};
use rstar::{Envelope, RTree, RTreeNum};

/// A `PreparedGeometry` can be more efficient than a plain Geometry when performing
/// multiple topological comparisons against the `PreparedGeometry`.
Expand All @@ -27,183 +27,74 @@ use rstar::{RTree, RTreeNum};
/// assert!(prepared_polygon.relate(&contained_line).is_contains());
///
/// ```
pub struct PreparedGeometry<'a, F: GeoFloat + RTreeNum = f64> {
pub struct PreparedGeometry<'a, G: Into<GeometryCow<'a, F>>, F: GeoFloat + RTreeNum = f64> {
pub(crate) geometry: G,
pub(crate) geometry_graph: GeometryGraph<'a, F>,
pub(crate) bounding_rect: Option<Rect<F>>,
}

mod conversions {
use crate::geometry_cow::GeometryCow;
use crate::relate::geomgraph::{GeometryGraph, RobustLineIntersector};
use crate::{BoundingRect, GeoFloat, PreparedGeometry};
use geo_types::{
Geometry, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, MultiPolygon,
Point, Polygon, Rect, Triangle,
use crate::geometry::*;
pub(crate) fn prepare_geometry<'a, F, T>(geometry: T) -> PreparedGeometry<'a, T, F>
where
F: GeoFloat,
T: Clone + Into<GeometryCow<'a, F>>,
{
let mut geometry_graph = GeometryGraph::new(0, geometry.clone().into());
let r_tree = geometry_graph.build_tree();
let envelope = r_tree.root().envelope();

// geo and rstar have different conventions for how to represet an empty bounding boxes
let bounding_rect: Option<Rect<F>> = if envelope == rstar::AABB::new_empty() {
None
} else {
Some(Rect::new(envelope.lower(), envelope.upper()))
};
use rstar::Envelope;
use std::rc::Rc;

impl<F: GeoFloat> From<Point<F>> for PreparedGeometry<'_, F> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These super repetitive implementations still exist, they were just moved to the same macro loop that spits out the impl Relate for each geometry, which is no less generated code, but much less typing.

https://github.com/georust/geo/pull/1318/files#diff-68ef9d4f18a931c4cac3275e080a0e37a6a12ec252e519820801e3fa3d716dfdR82

fn from(point: Point<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(point))
}
}
impl<F: GeoFloat> From<Line<F>> for PreparedGeometry<'_, F> {
fn from(line: Line<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(line))
}
}
impl<F: GeoFloat> From<LineString<F>> for PreparedGeometry<'_, F> {
fn from(line_string: LineString<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(line_string))
}
}
impl<F: GeoFloat> From<Polygon<F>> for PreparedGeometry<'_, F> {
fn from(polygon: Polygon<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(polygon))
}
}
impl<F: GeoFloat> From<MultiPoint<F>> for PreparedGeometry<'_, F> {
fn from(multi_point: MultiPoint<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(multi_point))
}
}
impl<F: GeoFloat> From<MultiLineString<F>> for PreparedGeometry<'_, F> {
fn from(multi_line_string: MultiLineString<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(multi_line_string))
}
}
impl<F: GeoFloat> From<MultiPolygon<F>> for PreparedGeometry<'_, F> {
fn from(multi_polygon: MultiPolygon<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(multi_polygon))
}
}
impl<F: GeoFloat> From<Rect<F>> for PreparedGeometry<'_, F> {
fn from(rect: Rect<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(rect))
}
}
impl<F: GeoFloat> From<Triangle<F>> for PreparedGeometry<'_, F> {
fn from(triangle: Triangle<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(triangle))
}
}
impl<F: GeoFloat> From<GeometryCollection<F>> for PreparedGeometry<'_, F> {
fn from(geometry_collection: GeometryCollection<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(geometry_collection))
}
}
impl<F: GeoFloat> From<Geometry<F>> for PreparedGeometry<'_, F> {
fn from(geometry: Geometry<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(geometry))
}
}

impl<'a, F: GeoFloat> From<&'a Point<F>> for PreparedGeometry<'a, F> {
fn from(point: &'a Point<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(point))
}
}
impl<'a, F: GeoFloat> From<&'a Line<F>> for PreparedGeometry<'a, F> {
fn from(line: &'a Line<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(line))
}
}
impl<'a, F: GeoFloat> From<&'a LineString<F>> for PreparedGeometry<'a, F> {
fn from(line_string: &'a LineString<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(line_string))
}
}
impl<'a, F: GeoFloat> From<&'a Polygon<F>> for PreparedGeometry<'a, F> {
fn from(polygon: &'a Polygon<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(polygon))
}
}
impl<'a, F: GeoFloat> From<&'a MultiPoint<F>> for PreparedGeometry<'a, F> {
fn from(multi_point: &'a MultiPoint<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(multi_point))
}
}
impl<'a, F: GeoFloat> From<&'a MultiLineString<F>> for PreparedGeometry<'a, F> {
fn from(multi_line_string: &'a MultiLineString<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(multi_line_string))
}
}
impl<'a, F: GeoFloat> From<&'a MultiPolygon<F>> for PreparedGeometry<'a, F> {
fn from(multi_polygon: &'a MultiPolygon<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(multi_polygon))
}
}
impl<'a, F: GeoFloat> From<&'a GeometryCollection<F>> for PreparedGeometry<'a, F> {
fn from(geometry_collection: &'a GeometryCollection<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(geometry_collection))
}
}
impl<'a, F: GeoFloat> From<&'a Rect<F>> for PreparedGeometry<'a, F> {
fn from(rect: &'a Rect<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(rect))
}
}
impl<'a, F: GeoFloat> From<&'a Triangle<F>> for PreparedGeometry<'a, F> {
fn from(triangle: &'a Triangle<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(triangle))
}
}

impl<'a, F: GeoFloat> From<&'a Geometry<F>> for PreparedGeometry<'a, F> {
fn from(geometry: &'a Geometry<F>) -> Self {
PreparedGeometry::from(GeometryCow::from(geometry))
}
}
// They should be equal - but we can save the computation in the `--release` case
// by using the bounding_rect from the RTree
debug_assert_eq!(bounding_rect, geometry_graph.geometry().bounding_rect());

impl<'a, F: GeoFloat> From<GeometryCow<'a, F>> for PreparedGeometry<'a, F> {
fn from(geometry: GeometryCow<'a, F>) -> Self {
let mut geometry_graph = GeometryGraph::new(0, geometry);
let r_tree = geometry_graph.build_tree();
geometry_graph.set_tree(Rc::new(r_tree));

let envelope = r_tree.root().envelope();

// geo and rstar have different conventions for how to represet an empty bounding boxes
let bounding_rect: Option<Rect<F>> = if envelope == rstar::AABB::new_empty() {
None
} else {
Some(Rect::new(envelope.lower(), envelope.upper()))
};
// They should be equal - but we can save the computation in the `--release` case
// by using the bounding_rect from the RTree
debug_assert_eq!(bounding_rect, geometry_graph.geometry().bounding_rect());

geometry_graph.set_tree(Rc::new(r_tree));

// TODO: don't pass in line intersector here - in theory we'll want pluggable line intersectors
// and the type (Robust) shouldn't be hard coded here.
geometry_graph.compute_self_nodes(Box::new(RobustLineIntersector::new()));
Self {
geometry_graph,
bounding_rect,
}
}
// TODO: don't pass in line intersector here - in theory we'll want pluggable line intersectors
// and the type (Robust) shouldn't be hard coded here.
geometry_graph.compute_self_nodes(Box::new(RobustLineIntersector::new()));
PreparedGeometry {
geometry,
geometry_graph,
bounding_rect,
}
}

impl<F> PreparedGeometry<'_, F>
impl<'a, G, F> PreparedGeometry<'a, G, F>
where
F: GeoFloat + RTreeNum,
G: Into<GeometryCow<'a, F>>,
{
pub(crate) fn geometry(&self) -> &GeometryCow<F> {
self.geometry_graph.geometry()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just reasoning through part of the diff: there was already a GeometryCow available buried deeper, but the types you're exposing here are a little different... Into<GeometryCow>, not &GeometryCow. So it's indeed necessary to store the new field, not just try and get it from geometry_graph

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. This was probably worth spelling out more in my PR, but I'll do it now:

An alternate approach that doesn't add a new field/generic to PreparedGeometry could use the existing reference from the GeometryGraph's GeometryCow.

The problem is, because GeometryCow is an enum (just like geo::Geometry) holding some geometry type, we don't have a nice way to get the specific geometry type back out. Instead, the api would have to be fallible, and look like:

geometry_cow.as_point() -> Option<Point>
geometry_cow.as_line_string() -> Option<LineString>
geometry_cow.as_polygon() -> Option<Polygon>
// etc.

For a given GeometryCow, exactly one of those would return Some, the rest would return None. As a programmer using this API you'd have to keep track of what kind of geometry is in each PreparedGeometry.

pub fn geometry(&self) -> &G {
&self.geometry
}
pub fn into_geometry(self) -> G {
self.geometry
}
}

impl<F: GeoFloat> BoundingRect<F> for PreparedGeometry<'_, F> {
impl<'a, G, F> BoundingRect<F> for PreparedGeometry<'a, G, F>
where
F: GeoFloat,
G: Into<GeometryCow<'a, F>>,
{
type Output = Option<Rect<F>>;

fn bounding_rect(&self) -> Option<Rect<F>> {
self.bounding_rect
}
}

impl<F: GeoFloat> HasDimensions for PreparedGeometry<'_, F> {
impl<'a, G, F: GeoFloat> HasDimensions for PreparedGeometry<'a, G, F>
where
F: GeoFloat,
G: Into<GeometryCow<'a, F>>,
{
fn is_empty(&self) -> bool {
self.geometry_graph.geometry().is_empty()
}
Expand All @@ -217,7 +108,11 @@ impl<F: GeoFloat> HasDimensions for PreparedGeometry<'_, F> {
}
}

impl<F: GeoFloat> Relate<F> for PreparedGeometry<'_, F> {
impl<'a, G, F: GeoFloat> Relate<F> for PreparedGeometry<'a, G, F>
where
F: GeoFloat,
G: Into<GeometryCow<'a, F>>,
{
/// Efficiently builds a [`GeometryGraph`] which can then be used for topological
/// computations.
fn geometry_graph(&self, arg_index: usize) -> GeometryGraph<F> {
Expand Down Expand Up @@ -265,4 +160,16 @@ mod tests {
let fresh_graph = GeometryGraph::new(1, poly_cow);
cached_graph.assert_eq_graph(&fresh_graph);
}

#[test]
fn get_geometry() {
let poly = polygon![(x: 0.0, y: 0.0), (x: 2.0, y: 0.0), (x: 1.0, y: 1.0)];
let prepared_geom = PreparedGeometry::from(&poly);
assert_eq!(&poly, *prepared_geom.geometry());
assert_eq!(&poly, prepared_geom.into_geometry());

let prepared_geom = PreparedGeometry::from(poly.clone());
assert_eq!(&poly, prepared_geom.geometry());
assert_eq!(poly, prepared_geom.into_geometry());
}
}
12 changes: 11 additions & 1 deletion geo/src/algorithm/relate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,17 @@ macro_rules! relate_impl {
$(
impl<F: GeoFloat> Relate<F> for $t {
fn geometry_graph(&self, arg_index: usize) -> GeometryGraph<F> {
GeometryGraph::new(arg_index, GeometryCow::from(self))
$crate::relate::GeometryGraph::new(arg_index, GeometryCow::from(self))
}
}
impl<F: GeoFloat> From<$t> for PreparedGeometry<'static, $t, F> {
fn from(geometry: $t) -> Self {
$crate::relate::geomgraph::index::prepare_geometry(geometry)
}
}
impl<'a, F: GeoFloat> From<&'a $t> for PreparedGeometry<'a, &'a $t, F> {
fn from(geometry: &'a $t) -> Self {
$crate::relate::geomgraph::index::prepare_geometry(geometry)
}
}
)*
Expand Down
2 changes: 1 addition & 1 deletion geo/src/geometry_cow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::borrow::Cow;
///
/// As an example, see the [`Relate`] trait which uses `GeometryCow`.
#[derive(PartialEq, Debug, Hash, Clone)]
pub(crate) enum GeometryCow<'a, T>
pub enum GeometryCow<'a, T>
where
T: CoordNum,
{
Expand Down