Skip to main content

geometry_algorithm/
envelope.rs

1//! `envelope(&g)` — the axis-aligned bounding box of `g`.
2//!
3//! Mirrors `boost::geometry::envelope` from
4//! `boost/geometry/algorithms/envelope.hpp` and the implementation
5//! ladder rooted at
6//! `boost/geometry/algorithms/detail/envelope/interface.hpp`. The
7//! Boost free function takes the bounding box by out-parameter
8//! (`envelope(g, mbr)`); the Rust analogue returns the box by value
9//! because we have no const-correctness story for an out-parameter
10//! that would make ownership any clearer than a return.
11//!
12//! Cartesian-only in v1; spherical / geographic envelope strategies
13//! arrive alongside the Haversine / Andoyer / Vincenty distance
14//! strategies in later tasks.
15
16use geometry_strategy::{EnvelopeStrategy, EnvelopeStrategyForKind};
17use geometry_trait::Geometry;
18
19/// Axis-aligned bounding box of `g`.
20///
21/// Mirrors `boost::geometry::envelope(g, mbr)` from
22/// `boost/geometry/algorithms/envelope.hpp` — the C++ side mutates
23/// `mbr` in place; the Rust side returns a fresh
24/// `geometry_model::Box<G::Point>`.
25///
26/// Supported geometry kinds: `Point`, `Linestring`, `Ring`, `Polygon`,
27/// `Segment`, `Box`, `MultiPoint`, `MultiLinestring`, `MultiPolygon` —
28/// each selected by the tag-keyed
29/// [`geometry_strategy::EnvelopeStrategyForKind`] picker, so any
30/// concept-adapted foreign type resolves through the same per-kind impl
31/// in [`geometry_strategy::envelope`] as the equivalent model value.
32#[inline]
33#[must_use]
34pub fn envelope<G>(
35    g: &G,
36) -> <<G::Kind as EnvelopeStrategyForKind>::S as EnvelopeStrategy<G>>::Output
37where
38    G: Geometry,
39    G::Kind: EnvelopeStrategyForKind,
40    <G::Kind as EnvelopeStrategyForKind>::S: EnvelopeStrategy<G>,
41{
42    <<G::Kind as EnvelopeStrategyForKind>::S as Default>::default().envelope(g)
43}
44
45#[cfg(test)]
46mod tests {
47    //! Reference values come from
48    //! `geometry/test/algorithms/envelope_expand/envelope.cpp:38-54`
49    //! (the `test_2d` arm). Each test cites the line it mirrors.
50
51    use super::envelope;
52    use geometry_cs::Cartesian;
53    use geometry_model::{Box, Linestring, Point2D, Polygon, Segment, linestring, polygon};
54    use geometry_trait::IndexedAccess as _;
55
56    type P = Point2D<f64, Cartesian>;
57
58    fn assert_2d(b: &Box<P>, xmin: f64, xmax: f64, ymin: f64, ymax: f64) {
59        assert_eq!(b.get_indexed::<0, 0>().to_bits(), xmin.to_bits());
60        assert_eq!(b.get_indexed::<0, 1>().to_bits(), ymin.to_bits());
61        assert_eq!(b.get_indexed::<1, 0>().to_bits(), xmax.to_bits());
62        assert_eq!(b.get_indexed::<1, 1>().to_bits(), ymax.to_bits());
63    }
64
65    /// `envelope.cpp:38` — `POINT(1 1)` → `(1,1) (1,1)`.
66    #[test]
67    fn point_envelope_collapses() {
68        let p = Point2D::<f64, Cartesian>::new(1.0, 1.0);
69        assert_2d(&envelope(&p), 1.0, 1.0, 1.0, 1.0);
70    }
71
72    /// `envelope.cpp:39` — `LINESTRING(1 1,2 2)` → `(1,1) (2,2)`.
73    #[test]
74    fn linestring_two_points() {
75        let ls: Linestring<P> = linestring![(1.0, 1.0), (2.0, 2.0)];
76        assert_2d(&envelope(&ls), 1.0, 2.0, 1.0, 2.0);
77    }
78
79    /// `envelope.cpp:40` — square polygon `(1,1)-(3,3)`.
80    #[test]
81    fn polygon_axis_aligned_square() {
82        let p: Polygon<P> = polygon![[(1.0, 1.0), (1.0, 3.0), (3.0, 3.0), (3.0, 1.0), (1.0, 1.0),]];
83        assert_2d(&envelope(&p), 1.0, 3.0, 1.0, 3.0);
84    }
85
86    /// `envelope.cpp:43` — `BOX(1 1,3 3)` — envelope is the box.
87    #[test]
88    fn box_envelope_is_self() {
89        let b = Box::from_corners(
90            Point2D::<f64, Cartesian>::new(1.0, 1.0),
91            Point2D::<f64, Cartesian>::new(3.0, 3.0),
92        );
93        assert_2d(&envelope(&b), 1.0, 3.0, 1.0, 3.0);
94    }
95
96    /// `envelope.cpp:48` — non-convex closed CW ring; envelope
97    /// tightens to the extremes `(0,1)-(7,9)`.
98    #[test]
99    fn ring_non_convex() {
100        let p: Polygon<P> = polygon![[(4.0, 1.0), (0.0, 7.0), (7.0, 9.0), (4.0, 1.0)]];
101        assert_2d(&envelope(&p), 0.0, 7.0, 1.0, 9.0);
102    }
103
104    /// `envelope.cpp:54` — `SEGMENT(1 1,3 3)`.
105    #[test]
106    fn segment_envelope() {
107        let s = Segment::new(
108            Point2D::<f64, Cartesian>::new(1.0, 1.0),
109            Point2D::<f64, Cartesian>::new(3.0, 3.0),
110        );
111        assert_2d(&envelope(&s), 1.0, 3.0, 1.0, 3.0);
112    }
113}