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}