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::{
29 LinestringTag, MultiLinestringTag, MultiPointTag, MultiPolygonTag, PolygonTag, RingTag,
30 };
31 pub use geometry_trait::{
32 Geometry, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Polygon, Ring,
33 };
34
35 pub use geometry_trait::{Closure, PointOrder};
36}
37
38/// Register a user-owned multi-point container.
39///
40/// Emits the `Geometry<Kind = MultiPointTag>` and [`geometry_trait::MultiPoint`]
41/// impls needed to expose the supplied point iterator. Mirrors
42/// `BOOST_GEOMETRY_REGISTER_MULTI_POINT` from
43/// `geometries/register/multi_point.hpp:36-40`; Rust additionally accepts the
44/// iterator expression because native containers do not share a Boost.Range
45/// base class.
46#[macro_export]
47macro_rules! register_multi_point {
48 ($Ty:ty, $Point:ty, |$value:ident| $iter:expr) => {
49 impl $crate::__macros::Geometry for $Ty {
50 type Kind = $crate::__macros::MultiPointTag;
51 type Point = $Point;
52 }
53
54 impl $crate::__macros::MultiPoint for $Ty {
55 type ItemPoint = $Point;
56
57 fn points(&self) -> impl ::core::iter::ExactSizeIterator<Item = &$Point> {
58 let $value = self;
59 $iter
60 }
61 }
62 };
63}
64
65/// Register a user-owned multi-linestring container.
66///
67/// Mirrors `BOOST_GEOMETRY_REGISTER_MULTI_LINESTRING` from
68/// `geometries/register/multi_linestring.hpp:36-40`. The item type and
69/// iterator are explicit on Rust's side because there is no common
70/// Boost.Range-style container base.
71#[macro_export]
72macro_rules! register_multi_linestring {
73 ($Ty:ty, $Point:ty, item = $Item:ty, |$value:ident| $iter:expr) => {
74 impl $crate::__macros::Geometry for $Ty {
75 type Kind = $crate::__macros::MultiLinestringTag;
76 type Point = $Point;
77 }
78
79 impl $crate::__macros::MultiLinestring for $Ty {
80 type ItemLinestring = $Item;
81
82 fn linestrings(&self) -> impl ::core::iter::ExactSizeIterator<Item = &$Item> {
83 let $value = self;
84 $iter
85 }
86 }
87 };
88}
89
90/// Register a user-owned multi-polygon container.
91///
92/// Mirrors `BOOST_GEOMETRY_REGISTER_MULTI_POLYGON` from
93/// `geometries/register/multi_polygon.hpp:36-40`. The item type and iterator
94/// make the collection shape explicit in the generated Rust concept impl.
95#[macro_export]
96macro_rules! register_multi_polygon {
97 ($Ty:ty, $Point:ty, item = $Item:ty, |$value:ident| $iter:expr) => {
98 impl $crate::__macros::Geometry for $Ty {
99 type Kind = $crate::__macros::MultiPolygonTag;
100 type Point = $Point;
101 }
102
103 impl $crate::__macros::MultiPolygon for $Ty {
104 type ItemPolygon = $Item;
105
106 fn polygons(&self) -> impl ::core::iter::ExactSizeIterator<Item = &$Item> {
107 let $value = self;
108 $iter
109 }
110 }
111 };
112}
113
114/// Register a user-owned linestring type whose storage is iterable as
115/// `&Point` via a user-supplied expression.
116///
117/// Emits `impl Geometry for $Ty` with `Kind = LinestringTag` and
118/// `Point = $Point`, plus `impl Linestring for $Ty` whose `points()`
119/// returns the iterator the closure-shaped argument produces.
120///
121/// Mirrors `BOOST_GEOMETRY_REGISTER_LINESTRING` from
122/// `boost/geometry/geometries/register/linestring.hpp`. The Boost
123/// macro only has to specialise `traits::tag<G>` because the rest of
124/// the linestring concept rides on `boost::range` for free; the Rust
125/// counterpart has to spell the iterator out, which is why the
126/// macro takes a closure-shaped expression for the iteration.
127///
128/// The iterator expression must yield an
129/// `ExactSizeIterator<Item = &$Point> + Clone` — the same bound
130/// [`geometry_trait::Linestring::points`] requires. For the common
131/// case of a `Vec<$Point>` field, `|s| s.field.iter()` is the right
132/// shape.
133///
134/// # Example
135///
136/// ```
137/// use geometry_adapt::register_linestring;
138/// use geometry_cs::Cartesian;
139/// use geometry_model::Point2D;
140/// use geometry_trait::Linestring;
141///
142/// struct MyLineString { points: Vec<Point2D<f64, Cartesian>> }
143///
144/// register_linestring!(MyLineString, Point2D<f64, Cartesian>, |s| s.points.iter());
145///
146/// let ls = MyLineString {
147/// points: vec![Point2D::new(0.0, 0.0), Point2D::new(1.0, 1.0)],
148/// };
149/// assert_eq!(ls.points().count(), 2);
150/// ```
151#[macro_export]
152macro_rules! register_linestring {
153 ($Ty:ty, $Point:ty, |$s:ident| $iter:expr) => {
154 impl $crate::__macros::Geometry for $Ty {
155 type Kind = $crate::__macros::LinestringTag;
156 type Point = $Point;
157 }
158 impl $crate::__macros::Linestring for $Ty {
159 fn points(
160 &self,
161 ) -> impl ::core::iter::ExactSizeIterator<Item = &$Point> + ::core::clone::Clone {
162 let $s = self;
163 $iter
164 }
165 }
166 };
167}
168
169/// Register a user-owned ring type whose storage is iterable as
170/// `&Point` via a user-supplied expression.
171///
172/// Emits `impl Geometry for $Ty` with `Kind = RingTag` and
173/// `Point = $Point`, plus `impl Ring for $Ty` whose `points()` returns
174/// the iterator the closure-shaped argument produces. Optional
175/// trailing `closure = …` and `point_order = …` clauses override the
176/// defaults [`geometry_trait::Ring`] inherits from
177/// `boost/geometry/core/{closure,point_order}.hpp` (closed,
178/// clockwise).
179///
180/// Mirrors `BOOST_GEOMETRY_REGISTER_RING` from
181/// `boost/geometry/geometries/register/ring.hpp`, plus the
182/// per-ring `traits::closure<R>` / `traits::point_order<R>`
183/// specialisations from `boost/geometry/core/closure.hpp` and
184/// `boost/geometry/core/point_order.hpp`.
185///
186/// # Examples
187///
188/// Default-orientation ring (`closed`, `clockwise`):
189///
190/// ```
191/// use geometry_adapt::register_ring;
192/// use geometry_cs::Cartesian;
193/// use geometry_model::Point2D;
194/// use geometry_trait::Ring;
195///
196/// struct MyRing { points: Vec<Point2D<f64, Cartesian>> }
197///
198/// register_ring!(MyRing, Point2D<f64, Cartesian>, |s| s.points.iter());
199///
200/// let r = MyRing {
201/// points: vec![
202/// Point2D::new(0.0, 0.0),
203/// Point2D::new(1.0, 0.0),
204/// Point2D::new(1.0, 1.0),
205/// Point2D::new(0.0, 0.0),
206/// ],
207/// };
208/// assert_eq!(r.points().count(), 4);
209/// ```
210///
211/// Override `closure` and `point_order`:
212///
213/// ```
214/// use geometry_adapt::register_ring;
215/// use geometry_cs::Cartesian;
216/// use geometry_model::Point2D;
217/// use geometry_trait::{Closure, PointOrder, Ring};
218///
219/// struct OpenCcwRing { points: Vec<Point2D<f64, Cartesian>> }
220///
221/// register_ring!(
222/// OpenCcwRing,
223/// Point2D<f64, Cartesian>,
224/// |s| s.points.iter(),
225/// closure = Closure::Open,
226/// point_order = PointOrder::CounterClockwise
227/// );
228///
229/// let r = OpenCcwRing { points: vec![] };
230/// assert_eq!(r.closure(), Closure::Open);
231/// assert_eq!(r.point_order(), PointOrder::CounterClockwise);
232/// ```
233#[macro_export]
234macro_rules! register_ring {
235 ($Ty:ty, $Point:ty, |$s:ident| $iter:expr) => {
236 impl $crate::__macros::Geometry for $Ty {
237 type Kind = $crate::__macros::RingTag;
238 type Point = $Point;
239 }
240 impl $crate::__macros::Ring for $Ty {
241 fn points(
242 &self,
243 ) -> impl ::core::iter::ExactSizeIterator<Item = &$Point> + ::core::clone::Clone {
244 let $s = self;
245 $iter
246 }
247 }
248 };
249 (
250 $Ty:ty,
251 $Point:ty,
252 |$s:ident| $iter:expr,
253 closure = $closure:expr,
254 point_order = $order:expr $(,)?
255 ) => {
256 impl $crate::__macros::Geometry for $Ty {
257 type Kind = $crate::__macros::RingTag;
258 type Point = $Point;
259 }
260 impl $crate::__macros::Ring for $Ty {
261 fn points(
262 &self,
263 ) -> impl ::core::iter::ExactSizeIterator<Item = &$Point> + ::core::clone::Clone {
264 let $s = self;
265 $iter
266 }
267 fn closure(&self) -> $crate::__macros::Closure {
268 $closure
269 }
270 fn point_order(&self) -> $crate::__macros::PointOrder {
271 $order
272 }
273 }
274 };
275}
276
277/// Register a user-owned polygon type whose interior decomposes as
278/// `{ outer: $Ring, inners: <iterable of $Ring> }`.
279///
280/// Emits `impl Geometry for $Ty` with `Kind = PolygonTag` and
281/// `Point = $Point`, plus `impl Polygon for $Ty` with the supplied
282/// `$Ring` as the associated `Ring` type. The `outer = …` expression
283/// must produce `&$Ring`; the `inners = …` expression must produce
284/// `impl ExactSizeIterator<Item = &$Ring>` — the bound
285/// [`geometry_trait::Polygon::interiors`] requires.
286///
287/// Mirrors the five-trait pattern in
288/// `doc/example_adapting_a_legacy_geometry_object_model.qbk`
289/// (`tag`, `ring_const_type`, `interior_const_type`, `exterior_ring`,
290/// `interior_rings`) — Boost.Geometry has no `register/polygon.hpp`,
291/// so this macro is the equivalent shortcut on the Rust side.
292///
293/// # Example
294///
295/// ```
296/// use geometry_adapt::{register_polygon, register_ring};
297/// use geometry_cs::Cartesian;
298/// use geometry_model::Point2D;
299/// use geometry_trait::{Polygon, Ring};
300///
301/// type P = Point2D<f64, Cartesian>;
302///
303/// struct MyRing { points: Vec<P> }
304/// register_ring!(MyRing, P, |s| s.points.iter());
305///
306/// struct MyPoly { outer: MyRing, inners: Vec<MyRing> }
307/// register_polygon!(
308/// MyPoly,
309/// P,
310/// ring = MyRing,
311/// |s| outer = &s.outer, inners = s.inners.iter()
312/// );
313///
314/// let poly = MyPoly {
315/// outer: MyRing { points: vec![] },
316/// inners: vec![],
317/// };
318/// assert_eq!(poly.exterior().points().count(), 0);
319/// assert_eq!(poly.interiors().count(), 0);
320/// ```
321#[macro_export]
322macro_rules! register_polygon {
323 (
324 $Ty:ty,
325 $Point:ty,
326 ring = $Ring:ty,
327 |$s:ident| outer = $outer:expr, inners = $inners:expr $(,)?
328 ) => {
329 impl $crate::__macros::Geometry for $Ty {
330 type Kind = $crate::__macros::PolygonTag;
331 type Point = $Point;
332 }
333 impl $crate::__macros::Polygon for $Ty {
334 type Ring = $Ring;
335
336 fn exterior(&self) -> &Self::Ring {
337 let $s = self;
338 $outer
339 }
340
341 fn interiors(&self) -> impl ::core::iter::ExactSizeIterator<Item = &Self::Ring> {
342 let $s = self;
343 $inners
344 }
345 }
346 };
347}