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}