Skip to main content

geometry_adapt/
macros.rs

1//! Declarative macros that register a user-owned container as a
2//! [`Linestring`], [`Ring`], or [`Polygon`].
3//!
4//! Mirrors the role of `BOOST_GEOMETRY_REGISTER_LINESTRING`
5//! (`boost/geometry/geometries/register/linestring.hpp`) and
6//! `BOOST_GEOMETRY_REGISTER_RING`
7//! (`boost/geometry/geometries/register/ring.hpp`). Boost has no
8//! `register/polygon.hpp` — adapting a polygon there is done by
9//! hand-specialising five traits (see
10//! `doc/example_adapting_a_legacy_geometry_object_model.qbk`); the
11//! [`register_polygon!`] macro consolidates that pattern into one
12//! declaration on the Rust side.
13//!
14//! Why macros instead of a blanket impl: Rust's orphan rule forbids
15//! `impl<P: Point, C: AsRef<[P]>> Linestring for C` from a downstream
16//! crate, so we mint the impl per-user-type with a macro — exactly
17//! the role the `BOOST_GEOMETRY_REGISTER_*` macros play in C++ for
18//! the same coherence reason (see proposal §3.7, Option D).
19//!
20//! The macros expand to references inside [`__macros`], a hidden
21//! re-export module, so the user's `use` graph never has to mention
22//! `geometry_trait` or `geometry_tag` directly.
23
24/// Hidden re-exports the macros expand to. Not part of the public
25/// API — users never name this path.
26#[doc(hidden)]
27pub mod __macros {
28    pub use geometry_tag::{LinestringTag, PolygonTag, RingTag};
29    pub use geometry_trait::{Geometry, Linestring, Polygon, Ring};
30
31    pub use geometry_trait::{Closure, PointOrder};
32}
33
34/// Register a user-owned linestring type whose storage is iterable as
35/// `&Point` via a user-supplied expression.
36///
37/// Emits `impl Geometry for $Ty` with `Kind = LinestringTag` and
38/// `Point = $Point`, plus `impl Linestring for $Ty` whose `points()`
39/// returns the iterator the closure-shaped argument produces.
40///
41/// Mirrors `BOOST_GEOMETRY_REGISTER_LINESTRING` from
42/// `boost/geometry/geometries/register/linestring.hpp`. The Boost
43/// macro only has to specialise `traits::tag<G>` because the rest of
44/// the linestring concept rides on `boost::range` for free; the Rust
45/// counterpart has to spell the iterator out, which is why the
46/// macro takes a closure-shaped expression for the iteration.
47///
48/// The iterator expression must yield an
49/// `ExactSizeIterator<Item = &$Point> + Clone` — the same bound
50/// [`geometry_trait::Linestring::points`] requires. For the common
51/// case of a `Vec<$Point>` field, `|s| s.field.iter()` is the right
52/// shape.
53///
54/// # Example
55///
56/// ```
57/// use geometry_adapt::register_linestring;
58/// use geometry_cs::Cartesian;
59/// use geometry_model::Point2D;
60/// use geometry_trait::Linestring;
61///
62/// struct MyLineString { points: Vec<Point2D<f64, Cartesian>> }
63///
64/// register_linestring!(MyLineString, Point2D<f64, Cartesian>, |s| s.points.iter());
65///
66/// let ls = MyLineString {
67///     points: vec![Point2D::new(0.0, 0.0), Point2D::new(1.0, 1.0)],
68/// };
69/// assert_eq!(ls.points().count(), 2);
70/// ```
71#[macro_export]
72macro_rules! register_linestring {
73    ($Ty:ty, $Point:ty, |$s:ident| $iter:expr) => {
74        impl $crate::__macros::Geometry for $Ty {
75            type Kind = $crate::__macros::LinestringTag;
76            type Point = $Point;
77        }
78        impl $crate::__macros::Linestring for $Ty {
79            fn points(
80                &self,
81            ) -> impl ::core::iter::ExactSizeIterator<Item = &$Point> + ::core::clone::Clone {
82                let $s = self;
83                $iter
84            }
85        }
86    };
87}
88
89/// Register a user-owned ring type whose storage is iterable as
90/// `&Point` via a user-supplied expression.
91///
92/// Emits `impl Geometry for $Ty` with `Kind = RingTag` and
93/// `Point = $Point`, plus `impl Ring for $Ty` whose `points()` returns
94/// the iterator the closure-shaped argument produces. Optional
95/// trailing `closure = …` and `point_order = …` clauses override the
96/// defaults [`geometry_trait::Ring`] inherits from
97/// `boost/geometry/core/{closure,point_order}.hpp` (closed,
98/// clockwise).
99///
100/// Mirrors `BOOST_GEOMETRY_REGISTER_RING` from
101/// `boost/geometry/geometries/register/ring.hpp`, plus the
102/// per-ring `traits::closure<R>` / `traits::point_order<R>`
103/// specialisations from `boost/geometry/core/closure.hpp` and
104/// `boost/geometry/core/point_order.hpp`.
105///
106/// # Examples
107///
108/// Default-orientation ring (`closed`, `clockwise`):
109///
110/// ```
111/// use geometry_adapt::register_ring;
112/// use geometry_cs::Cartesian;
113/// use geometry_model::Point2D;
114/// use geometry_trait::Ring;
115///
116/// struct MyRing { points: Vec<Point2D<f64, Cartesian>> }
117///
118/// register_ring!(MyRing, Point2D<f64, Cartesian>, |s| s.points.iter());
119///
120/// let r = MyRing {
121///     points: vec![
122///         Point2D::new(0.0, 0.0),
123///         Point2D::new(1.0, 0.0),
124///         Point2D::new(1.0, 1.0),
125///         Point2D::new(0.0, 0.0),
126///     ],
127/// };
128/// assert_eq!(r.points().count(), 4);
129/// ```
130///
131/// Override `closure` and `point_order`:
132///
133/// ```
134/// use geometry_adapt::register_ring;
135/// use geometry_cs::Cartesian;
136/// use geometry_model::Point2D;
137/// use geometry_trait::{Closure, PointOrder, Ring};
138///
139/// struct OpenCcwRing { points: Vec<Point2D<f64, Cartesian>> }
140///
141/// register_ring!(
142///     OpenCcwRing,
143///     Point2D<f64, Cartesian>,
144///     |s| s.points.iter(),
145///     closure = Closure::Open,
146///     point_order = PointOrder::CounterClockwise
147/// );
148///
149/// let r = OpenCcwRing { points: vec![] };
150/// assert_eq!(r.closure(), Closure::Open);
151/// assert_eq!(r.point_order(), PointOrder::CounterClockwise);
152/// ```
153#[macro_export]
154macro_rules! register_ring {
155    ($Ty:ty, $Point:ty, |$s:ident| $iter:expr) => {
156        impl $crate::__macros::Geometry for $Ty {
157            type Kind = $crate::__macros::RingTag;
158            type Point = $Point;
159        }
160        impl $crate::__macros::Ring for $Ty {
161            fn points(
162                &self,
163            ) -> impl ::core::iter::ExactSizeIterator<Item = &$Point> + ::core::clone::Clone {
164                let $s = self;
165                $iter
166            }
167        }
168    };
169    (
170        $Ty:ty,
171        $Point:ty,
172        |$s:ident| $iter:expr,
173        closure = $closure:expr,
174        point_order = $order:expr $(,)?
175    ) => {
176        impl $crate::__macros::Geometry for $Ty {
177            type Kind = $crate::__macros::RingTag;
178            type Point = $Point;
179        }
180        impl $crate::__macros::Ring for $Ty {
181            fn points(
182                &self,
183            ) -> impl ::core::iter::ExactSizeIterator<Item = &$Point> + ::core::clone::Clone {
184                let $s = self;
185                $iter
186            }
187            fn closure(&self) -> $crate::__macros::Closure {
188                $closure
189            }
190            fn point_order(&self) -> $crate::__macros::PointOrder {
191                $order
192            }
193        }
194    };
195}
196
197/// Register a user-owned polygon type whose interior decomposes as
198/// `{ outer: $Ring, inners: <iterable of $Ring> }`.
199///
200/// Emits `impl Geometry for $Ty` with `Kind = PolygonTag` and
201/// `Point = $Point`, plus `impl Polygon for $Ty` with the supplied
202/// `$Ring` as the associated `Ring` type. The `outer = …` expression
203/// must produce `&$Ring`; the `inners = …` expression must produce
204/// `impl ExactSizeIterator<Item = &$Ring>` — the bound
205/// [`geometry_trait::Polygon::interiors`] requires.
206///
207/// Mirrors the five-trait pattern in
208/// `doc/example_adapting_a_legacy_geometry_object_model.qbk`
209/// (`tag`, `ring_const_type`, `interior_const_type`, `exterior_ring`,
210/// `interior_rings`) — Boost.Geometry has no `register/polygon.hpp`,
211/// so this macro is the equivalent shortcut on the Rust side.
212///
213/// # Example
214///
215/// ```
216/// use geometry_adapt::{register_polygon, register_ring};
217/// use geometry_cs::Cartesian;
218/// use geometry_model::Point2D;
219/// use geometry_trait::{Polygon, Ring};
220///
221/// type P = Point2D<f64, Cartesian>;
222///
223/// struct MyRing { points: Vec<P> }
224/// register_ring!(MyRing, P, |s| s.points.iter());
225///
226/// struct MyPoly { outer: MyRing, inners: Vec<MyRing> }
227/// register_polygon!(
228///     MyPoly,
229///     P,
230///     ring = MyRing,
231///     |s| outer = &s.outer, inners = s.inners.iter()
232/// );
233///
234/// let poly = MyPoly {
235///     outer: MyRing { points: vec![] },
236///     inners: vec![],
237/// };
238/// assert_eq!(poly.exterior().points().count(), 0);
239/// assert_eq!(poly.interiors().count(), 0);
240/// ```
241#[macro_export]
242macro_rules! register_polygon {
243    (
244        $Ty:ty,
245        $Point:ty,
246        ring = $Ring:ty,
247        |$s:ident| outer = $outer:expr, inners = $inners:expr $(,)?
248    ) => {
249        impl $crate::__macros::Geometry for $Ty {
250            type Kind = $crate::__macros::PolygonTag;
251            type Point = $Point;
252        }
253        impl $crate::__macros::Polygon for $Ty {
254            type Ring = $Ring;
255
256            fn exterior(&self) -> &Self::Ring {
257                let $s = self;
258                $outer
259            }
260
261            fn interiors(&self) -> impl ::core::iter::ExactSizeIterator<Item = &Self::Ring> {
262                let $s = self;
263                $inners
264            }
265        }
266    };
267}