Skip to main content

geometry_algorithm/
convert.rs

1//! `convert(src) -> Dst` — typed conversions between equivalent
2//! geometry kinds.
3//!
4//! Mirrors `boost::geometry::convert(src, dst)` from
5//! `boost/geometry/algorithms/convert.hpp`. Boost writes the
6//! destination through an out-parameter; the Rust port returns by
7//! value. A dedicated [`Convert`] trait (rather than
8//! [`core::convert::Into`]) keeps geometry conversions opt-in and
9//! sidesteps the orphan-rule clash a blanket `From`/`Into` would hit
10//! the moment a user adapts their own types.
11//!
12//! Supported pairs:
13//!
14//! * `Box`     → `Polygon`     — the rectangle's 5-point closed ring
15//! * `Ring`    → `Polygon`     — the ring becomes the exterior
16//! * `Ring`    → `Linestring`  — copy the point sequence
17//! * `Segment` → `Linestring`  — two-point `(start, end)`
18//! * `Point`, `Linestring`, `Polygon` → single-member multi
19//!
20//! `Linestring` → `Ring` is intentionally NOT shipped — the caller
21//! must assert closure and orientation for the destination, which
22//! Boost forces through template arguments anyway. Use
23//! `Ring::from_vec(ls.0.clone())` (then [`correct`](fn@crate::correct))
24//! if you need it.
25
26use geometry_model::{
27    Box, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Polygon, Ring, Segment,
28};
29use geometry_trait::{Point as PointTrait, PointMut};
30
31use crate::make::make_point;
32
33/// Convert `src` into the destination kind `Dst`.
34///
35/// Mirrors `boost::geometry::convert(src, dst)` from
36/// `boost/geometry/algorithms/convert.hpp`. The destination is
37/// inferred from the call-site annotation:
38///
39/// ```ignore
40/// let pg: Polygon<P> = convert(&bx);
41/// ```
42#[must_use]
43pub fn convert<Src, Dst>(src: &Src) -> Dst
44where
45    Src: Convert<Dst>,
46{
47    src.convert()
48}
49
50/// Per-pair conversion dispatch. One impl per `(Src, Dst)` pair the
51/// port supports; the free [`convert`] function is the public entry.
52#[doc(hidden)]
53pub trait Convert<Dst> {
54    fn convert(&self) -> Dst;
55}
56
57/// `Box` → `Polygon`: the rectangle's four corners wound clockwise
58/// and closed back onto the first, giving a 5-point exterior ring.
59///
60/// Mirrors the `box → polygon` arm of
61/// `boost/geometry/algorithms/convert.hpp`. Only the first two
62/// dimensions participate — a box is a planar rectangle.
63impl<P, const CW: bool, const CL: bool> Convert<Polygon<P, CW, CL>> for Box<P>
64where
65    P: PointMut + Default,
66{
67    fn convert(&self) -> Polygon<P, CW, CL> {
68        let min_x = self.min().get::<0>();
69        let min_y = self.min().get::<1>();
70        let max_x = self.max().get::<0>();
71        let max_y = self.max().get::<1>();
72        // Clockwise from the minimum corner, closed back to the start:
73        // (minx, miny) → (minx, maxy) → (maxx, maxy) → (maxx, miny) → (minx, miny).
74        let ring = Ring::<P, CW, CL>::from_vec(alloc::vec![
75            make_point(&[min_x, min_y]),
76            make_point(&[min_x, max_y]),
77            make_point(&[max_x, max_y]),
78            make_point(&[max_x, min_y]),
79            make_point(&[min_x, min_y]),
80        ]);
81        Polygon::new(ring)
82    }
83}
84
85/// `Ring` → `Polygon`: the ring becomes the exterior, no holes.
86///
87/// Mirrors the `ring → polygon` arm of
88/// `boost/geometry/algorithms/convert.hpp`.
89impl<P, const CW: bool, const CL: bool> Convert<Polygon<P, CW, CL>> for Ring<P, CW, CL>
90where
91    P: PointTrait + Copy,
92{
93    fn convert(&self) -> Polygon<P, CW, CL> {
94        Polygon::new(Ring::from_vec(self.0.clone()))
95    }
96}
97
98/// `Ring` → `Linestring`: copy the point sequence, dropping the
99/// closure / orientation type information.
100///
101/// Mirrors the `ring → linestring` arm of
102/// `boost/geometry/algorithms/convert.hpp`.
103impl<P, const CW: bool, const CL: bool> Convert<Linestring<P>> for Ring<P, CW, CL>
104where
105    P: PointTrait + Copy,
106{
107    fn convert(&self) -> Linestring<P> {
108        Linestring(self.0.clone())
109    }
110}
111
112/// `Segment` → `Linestring`: a two-point `(start, end)` linestring.
113///
114/// Mirrors the `segment → linestring` arm of
115/// `boost/geometry/algorithms/convert.hpp`.
116impl<P> Convert<Linestring<P>> for Segment<P>
117where
118    P: PointTrait + Copy,
119{
120    fn convert(&self) -> Linestring<P> {
121        Linestring(alloc::vec![*self.start(), *self.end()])
122    }
123}
124
125/// `Point` → `MultiPoint`: wrap as a single-member multi.
126///
127/// Mirrors the `point → multi_point` arm of
128/// `boost/geometry/algorithms/convert.hpp`.
129impl<P> Convert<MultiPoint<P>> for P
130where
131    P: PointTrait + Copy,
132{
133    fn convert(&self) -> MultiPoint<P> {
134        MultiPoint(alloc::vec![*self])
135    }
136}
137
138/// `Linestring` → `MultiLinestring`: wrap as a single-member multi.
139///
140/// Mirrors the `linestring → multi_linestring` arm of
141/// `boost/geometry/algorithms/convert.hpp`.
142impl<P> Convert<MultiLinestring<Linestring<P>>> for Linestring<P>
143where
144    P: PointTrait + Copy,
145{
146    fn convert(&self) -> MultiLinestring<Linestring<P>> {
147        MultiLinestring(alloc::vec![self.clone()])
148    }
149}
150
151/// `Polygon` → `MultiPolygon`: wrap as a single-member multi.
152///
153/// Mirrors the `polygon → multi_polygon` arm of
154/// `boost/geometry/algorithms/convert.hpp`.
155impl<P, const CW: bool, const CL: bool> Convert<MultiPolygon<Polygon<P, CW, CL>>>
156    for Polygon<P, CW, CL>
157where
158    P: PointTrait + Copy,
159{
160    fn convert(&self) -> MultiPolygon<Polygon<P, CW, CL>> {
161        MultiPolygon(alloc::vec![self.clone()])
162    }
163}
164
165#[cfg(test)]
166#[allow(
167    clippy::float_cmp,
168    reason = "Converted corner coordinates are exact literals."
169)]
170mod tests {
171    //! Reference behaviour from
172    //! `boost/geometry/test/algorithms/convert.cpp` — a box becomes a
173    //! 5-point closed polygon over the same corners; a segment becomes
174    //! a two-point linestring; a point wraps into a single-member
175    //! multi-point.
176
177    use super::convert;
178    use geometry_cs::Cartesian;
179    use geometry_model::{Box, Linestring, MultiPoint, Point2D, Polygon, Segment};
180    use geometry_trait::{Point as _, Polygon as _, Ring as _};
181
182    type Pt = Point2D<f64, Cartesian>;
183
184    // convert.cpp — Box → Polygon yields the rectangle's closed ring.
185    #[test]
186    fn box_to_polygon_five_points_and_corners() {
187        let b: Box<Pt> = Box::from_corners(Pt::new(0., 0.), Pt::new(4., 3.));
188        let pg: Polygon<Pt> = convert(&b);
189        let pts: alloc::vec::Vec<(f64, f64)> = pg
190            .exterior()
191            .points()
192            .map(|p| (p.get::<0>(), p.get::<1>()))
193            .collect();
194        // Closed ring: 5 points, first == last, all four corners present.
195        assert_eq!(pts.len(), 5);
196        assert_eq!(pts[0], (0., 0.));
197        assert_eq!(pts[4], (0., 0.));
198        assert!(pts.contains(&(0., 3.)));
199        assert!(pts.contains(&(4., 3.)));
200        assert!(pts.contains(&(4., 0.)));
201    }
202
203    // convert.cpp — Segment → Linestring keeps both endpoints.
204    #[test]
205    fn segment_to_linestring_endpoints() {
206        let s = Segment::new(Pt::new(0., 0.), Pt::new(3., 4.));
207        let ls: Linestring<Pt> = convert(&s);
208        assert_eq!(ls.0.len(), 2);
209        assert_eq!(ls.0[0].get::<0>(), 0.);
210        assert_eq!(ls.0[1].get::<0>(), 3.);
211    }
212
213    #[test]
214    fn point_to_multi_point_single_member() {
215        let mp: MultiPoint<Pt> = convert(&Pt::new(1., 2.));
216        assert_eq!(mp.0.len(), 1);
217        assert_eq!(mp.0[0].get::<0>(), 1.);
218    }
219
220    // convert.cpp — Ring → Polygon: the ring becomes a hole-free
221    // exterior, vertices copied verbatim.
222    #[test]
223    fn ring_to_polygon_becomes_hole_free_exterior() {
224        use geometry_model::Ring;
225        let ring: Ring<Pt> = Ring::from_vec(vec![
226            Pt::new(0., 0.),
227            Pt::new(1., 0.),
228            Pt::new(1., 1.),
229            Pt::new(0., 0.),
230        ]);
231        let pg: Polygon<Pt> = convert(&ring);
232        assert_eq!(pg.exterior().points().count(), 4);
233        assert_eq!(pg.interiors().count(), 0);
234        let first = pg.exterior().points().next().unwrap();
235        assert_eq!((first.get::<0>(), first.get::<1>()), (0., 0.));
236    }
237
238    // convert.cpp — Ring → Linestring copies the point sequence.
239    #[test]
240    fn ring_to_linestring_copies_points() {
241        use geometry_model::Ring;
242        let ring: Ring<Pt> =
243            Ring::from_vec(vec![Pt::new(2., 3.), Pt::new(4., 5.), Pt::new(2., 3.)]);
244        let ls: Linestring<Pt> = convert(&ring);
245        assert_eq!(ls.0.len(), 3);
246        assert_eq!((ls.0[1].get::<0>(), ls.0[1].get::<1>()), (4., 5.));
247    }
248
249    // convert.cpp — Linestring → MultiLinestring wraps a single member.
250    #[test]
251    fn linestring_to_multi_linestring_single_member() {
252        use geometry_model::MultiLinestring;
253        let ls: Linestring<Pt> = Linestring(vec![Pt::new(0., 0.), Pt::new(1., 1.)]);
254        let mls: MultiLinestring<Linestring<Pt>> = convert(&ls);
255        assert_eq!(mls.0.len(), 1);
256        assert_eq!(mls.0[0].0.len(), 2);
257    }
258
259    // convert.cpp — Polygon → MultiPolygon wraps a single member.
260    #[test]
261    fn polygon_to_multi_polygon_single_member() {
262        use geometry_model::{MultiPolygon, Ring};
263        let pg: Polygon<Pt> = Polygon::new(Ring::from_vec(vec![
264            Pt::new(0., 0.),
265            Pt::new(1., 0.),
266            Pt::new(1., 1.),
267            Pt::new(0., 0.),
268        ]));
269        let mpg: MultiPolygon<Polygon<Pt>> = convert(&pg);
270        assert_eq!(mpg.0.len(), 1);
271        assert_eq!(mpg.0[0].exterior().points().count(), 4);
272    }
273}