geometry_trait/multi.rs
1//! The three "iter of a single geometry" concepts:
2//! [`MultiPoint`], [`MultiLinestring`], and [`MultiPolygon`].
3//!
4//! Mirrors `doc/concept/multi_point.qbk`, `doc/concept/multi_linestring.qbk`,
5//! and `doc/concept/multi_polygon.qbk`; the canonical models live in
6//! `boost/geometry/geometries/multi_point.hpp`,
7//! `boost/geometry/geometries/multi_linestring.hpp`, and
8//! `boost/geometry/geometries/multi_polygon.hpp` respectively. All three
9//! C++ models derive from `Container<Item, Allocator<Item>>` and are
10//! consumed through `boost::range`; the Rust counterparts surface the
11//! item sequence as a `…s()` iterator returned via RPITIT, matching the
12//! pattern already used by [`crate::Linestring`], [`crate::Ring`], and
13//! [`crate::Polygon`].
14
15use crate::geometry::Geometry;
16use crate::linestring::Linestring;
17use crate::point::Point;
18use crate::polygon::Polygon;
19use geometry_tag::{MultiLinestringTag, MultiPointTag, MultiPolygonTag};
20
21/// A multi-point — a collection of points belonging to each other.
22///
23/// Mirrors the `MultiPoint` concept (`doc/concept/multi_point.qbk`); the
24/// canonical model is `boost::geometry::model::multi_point` in
25/// `boost/geometry/geometries/multi_point.hpp`, which derives from
26/// `std::vector<Point>` and asserts `concepts::Point<Point>` on its
27/// element type. The Rust port mirrors that assertion by bounding
28/// [`MultiPoint::ItemPoint`] on the [`Point`] concept.
29///
30/// As with [`crate::Linestring`], the element sequence is exposed via
31/// RPITIT so each impl can hand back whatever iterator its underlying
32/// container provides. The `ExactSizeIterator` bound mirrors the
33/// random-access shape `boost::range` relies on for the C++ model:
34/// callers can ask for `.len()` without consuming the source.
35///
36/// # Examples
37///
38/// ```
39/// use geometry_trait::MultiPoint;
40/// fn count<M: MultiPoint>(m: &M) -> usize { m.points().len() }
41/// ```
42pub trait MultiPoint: Geometry<Kind = MultiPointTag> {
43 /// The element point type.
44 ///
45 /// Mirrors the `Point` template parameter on
46 /// `boost::geometry::model::multi_point<Point, …>`
47 /// (`boost/geometry/geometries/multi_point.hpp`). The Rust side
48 /// does not require `ItemPoint == Self::Point`: the C++ model
49 /// has no `Self::Point` projection of its own (its
50 /// `point_type<MP>::type` is read from the element), so any
51 /// [`Point`] is admissible here.
52 type ItemPoint: Point;
53
54 /// The points of this multi-point, in declared order.
55 ///
56 /// Plays the role of `boost::begin(mp)` / `boost::end(mp)` from
57 /// `boost/geometry/geometries/multi_point.hpp` when read together.
58 fn points(&self) -> impl ExactSizeIterator<Item = &Self::ItemPoint>;
59}
60
61/// A multi-linestring — a collection of linestrings belonging to each
62/// other (e.g. a highway with interruptions).
63///
64/// Mirrors the `MultiLinestring` concept
65/// (`doc/concept/multi_linestring.qbk`); the canonical model is
66/// `boost::geometry::model::multi_linestring` in
67/// `boost/geometry/geometries/multi_linestring.hpp`, which derives
68/// from `std::vector<LineString>` and asserts
69/// `concepts::Linestring<LineString>` on its element type. The Rust
70/// port mirrors that assertion by bounding
71/// [`MultiLinestring::ItemLinestring`] on the [`Linestring`] concept,
72/// and additionally pins `ItemLinestring::Point = Self::Point` so the
73/// multi's `point_type<MLS>` projection (read through any element)
74/// stays consistent across the collection.
75///
76/// # Examples
77///
78/// ```
79/// use geometry_trait::MultiLinestring;
80/// fn count<M: MultiLinestring>(m: &M) -> usize { m.linestrings().len() }
81/// ```
82pub trait MultiLinestring: Geometry<Kind = MultiLinestringTag> {
83 /// The element linestring type.
84 ///
85 /// Mirrors the `LineString` template parameter on
86 /// `boost::geometry::model::multi_linestring<LineString, …>`
87 /// (`boost/geometry/geometries/multi_linestring.hpp`).
88 type ItemLinestring: Linestring<Point = Self::Point>;
89
90 /// The linestrings of this multi-linestring, in declared order.
91 ///
92 /// Plays the role of `boost::begin(mls)` / `boost::end(mls)` from
93 /// `boost/geometry/geometries/multi_linestring.hpp` when read
94 /// together.
95 fn linestrings(&self) -> impl ExactSizeIterator<Item = &Self::ItemLinestring>;
96}
97
98/// A multi-polygon — a collection of polygons belonging to each other
99/// (e.g. Hawaii).
100///
101/// Mirrors the `MultiPolygon` concept (`doc/concept/multi_polygon.qbk`);
102/// the canonical model is `boost::geometry::model::multi_polygon` in
103/// `boost/geometry/geometries/multi_polygon.hpp`, which derives from
104/// `std::vector<Polygon>` and asserts `concepts::Polygon<Polygon>` on
105/// its element type. The Rust port mirrors that assertion by bounding
106/// [`MultiPolygon::ItemPolygon`] on the [`Polygon`] concept, and
107/// additionally pins `ItemPolygon::Point = Self::Point` so the multi's
108/// `point_type<MPG>` projection stays consistent across the collection.
109///
110/// # Examples
111///
112/// ```
113/// use geometry_trait::MultiPolygon;
114/// fn count<M: MultiPolygon>(m: &M) -> usize { m.polygons().len() }
115/// ```
116pub trait MultiPolygon: Geometry<Kind = MultiPolygonTag> {
117 /// The element polygon type.
118 ///
119 /// Mirrors the `Polygon` template parameter on
120 /// `boost::geometry::model::multi_polygon<Polygon, …>`
121 /// (`boost/geometry/geometries/multi_polygon.hpp`).
122 type ItemPolygon: Polygon<Point = Self::Point>;
123
124 /// The polygons of this multi-polygon, in declared order.
125 ///
126 /// Plays the role of `boost::begin(mpg)` / `boost::end(mpg)` from
127 /// `boost/geometry/geometries/multi_polygon.hpp` when read
128 /// together.
129 fn polygons(&self) -> impl ExactSizeIterator<Item = &Self::ItemPolygon>;
130}
131
132#[cfg(test)]
133mod tests {
134 extern crate alloc;
135
136 use super::*;
137 use crate::point::PointMut;
138 use crate::ring::Ring;
139 use alloc::vec;
140 use alloc::vec::Vec;
141 use geometry_cs::Cartesian;
142 use geometry_tag::{LinestringTag, PointTag, PolygonTag, RingTag};
143
144 // --- Witnesses: any T satisfying the trait is accepted. These
145 // are static, compile-time checks; they never run. ---
146 fn accepts_mp<M: MultiPoint>() {}
147 fn accepts_mls<M: MultiLinestring>() {}
148 fn accepts_mpg<M: MultiPolygon>() {}
149
150 // --- Shared 2D Cartesian point used by every test impl below. ---
151 #[derive(Clone)]
152 struct Xy(f64, f64);
153
154 impl Geometry for Xy {
155 type Kind = PointTag;
156 type Point = Self;
157 }
158
159 impl Point for Xy {
160 type Scalar = f64;
161 type Cs = Cartesian;
162 const DIM: usize = 2;
163
164 fn get<const D: usize>(&self) -> f64 {
165 if D == 0 { self.0 } else { self.1 }
166 }
167 }
168
169 impl PointMut for Xy {
170 fn set<const D: usize>(&mut self, v: f64) {
171 if D == 0 {
172 self.0 = v;
173 } else {
174 self.1 = v;
175 }
176 }
177 }
178
179 // --- Vec-backed MultiPoint. ---
180 struct VMp(Vec<Xy>);
181
182 impl Geometry for VMp {
183 type Kind = MultiPointTag;
184 type Point = Xy;
185 }
186
187 impl MultiPoint for VMp {
188 type ItemPoint = Xy;
189
190 fn points(&self) -> impl ExactSizeIterator<Item = &Xy> {
191 self.0.iter()
192 }
193 }
194
195 #[test]
196 fn vec_backed_multipoint_satisfies_trait() {
197 let mp = VMp(vec![Xy(0.0, 0.0), Xy(1.0, 2.0), Xy(3.0, 4.0)]);
198 accepts_mp::<VMp>();
199 assert_eq!(mp.points().len(), 3);
200 let xs: Vec<f64> = mp.points().map(Xy::get::<0>).collect();
201 assert_eq!(xs, vec![0.0, 1.0, 3.0]);
202 }
203
204 // --- Vec-backed MultiLinestring. ---
205 struct VLs(Vec<Xy>);
206
207 impl Geometry for VLs {
208 type Kind = LinestringTag;
209 type Point = Xy;
210 }
211
212 impl Linestring for VLs {
213 fn points(&self) -> impl ExactSizeIterator<Item = &Xy> + Clone {
214 self.0.iter()
215 }
216 }
217
218 struct VMls(Vec<VLs>);
219
220 impl Geometry for VMls {
221 type Kind = MultiLinestringTag;
222 type Point = Xy;
223 }
224
225 impl MultiLinestring for VMls {
226 type ItemLinestring = VLs;
227
228 fn linestrings(&self) -> impl ExactSizeIterator<Item = &VLs> {
229 self.0.iter()
230 }
231 }
232
233 #[test]
234 fn vec_backed_multilinestring_satisfies_trait() {
235 let mls = VMls(vec![
236 VLs(vec![Xy(0.0, 0.0), Xy(1.0, 1.0)]),
237 VLs(vec![Xy(2.0, 2.0), Xy(3.0, 3.0), Xy(4.0, 4.0)]),
238 ]);
239 accepts_mls::<VMls>();
240 assert_eq!(mls.linestrings().len(), 2);
241 let counts: Vec<usize> = mls.linestrings().map(|ls| ls.points().count()).collect();
242 assert_eq!(counts, vec![2, 3]);
243 }
244
245 // --- Vec-backed MultiPolygon. ---
246 struct VRing(Vec<Xy>);
247
248 impl Geometry for VRing {
249 type Kind = RingTag;
250 type Point = Xy;
251 }
252
253 impl Ring for VRing {
254 fn points(&self) -> impl ExactSizeIterator<Item = &Xy> + Clone {
255 self.0.iter()
256 }
257 }
258
259 struct VPoly {
260 outer: VRing,
261 inners: Vec<VRing>,
262 }
263
264 impl Geometry for VPoly {
265 type Kind = PolygonTag;
266 type Point = Xy;
267 }
268
269 impl Polygon for VPoly {
270 type Ring = VRing;
271
272 fn exterior(&self) -> &VRing {
273 &self.outer
274 }
275
276 fn interiors(&self) -> impl ExactSizeIterator<Item = &VRing> {
277 self.inners.iter()
278 }
279 }
280
281 struct VMpg(Vec<VPoly>);
282
283 impl Geometry for VMpg {
284 type Kind = MultiPolygonTag;
285 type Point = Xy;
286 }
287
288 impl MultiPolygon for VMpg {
289 type ItemPolygon = VPoly;
290
291 fn polygons(&self) -> impl ExactSizeIterator<Item = &VPoly> {
292 self.0.iter()
293 }
294 }
295
296 #[test]
297 fn vec_backed_multipolygon_satisfies_trait() {
298 let mpg = VMpg(vec![
299 VPoly {
300 outer: VRing(vec![
301 Xy(0.0, 0.0),
302 Xy(1.0, 0.0),
303 Xy(1.0, 1.0),
304 Xy(0.0, 1.0),
305 Xy(0.0, 0.0),
306 ]),
307 inners: vec![],
308 },
309 VPoly {
310 outer: VRing(vec![
311 Xy(2.0, 2.0),
312 Xy(3.0, 2.0),
313 Xy(3.0, 3.0),
314 Xy(2.0, 3.0),
315 Xy(2.0, 2.0),
316 ]),
317 inners: vec![VRing(vec![
318 Xy(2.25, 2.25),
319 Xy(2.75, 2.25),
320 Xy(2.75, 2.75),
321 Xy(2.25, 2.75),
322 Xy(2.25, 2.25),
323 ])],
324 },
325 ]);
326 accepts_mpg::<VMpg>();
327 assert_eq!(mpg.polygons().len(), 2);
328 let inner_counts: Vec<usize> = mpg.polygons().map(|p| p.interiors().count()).collect();
329 assert_eq!(inner_counts, vec![0, 1]);
330
331 // Read the exterior ring's vertices of the first polygon through
332 // `VRing::points` and both `Xy::get::<0>` / `get::<1>`.
333 let first = mpg.polygons().next().unwrap();
334 let ext: Vec<(f64, f64)> = first
335 .exterior()
336 .points()
337 .map(|p| (p.get::<0>(), p.get::<1>()))
338 .collect();
339 assert_eq!(ext.len(), 5);
340 assert_eq!(ext[2], (1.0, 1.0));
341
342 // And the second polygon's single hole is a non-empty ring.
343 let hole_len = mpg
344 .polygons()
345 .nth(1)
346 .unwrap()
347 .interiors()
348 .next()
349 .unwrap()
350 .points()
351 .count();
352 assert_eq!(hole_len, 5);
353 }
354
355 /// `Xy` is a full `PointMut`: `set` writes each ordinate and `get`
356 /// reads it back — covering the `get`/`set` `D == 1` branches.
357 #[test]
358 #[allow(
359 clippy::float_cmp,
360 reason = "compared values are exact integer-valued literals"
361 )]
362 fn xy_get_set_round_trips_both_ordinates() {
363 let mut p = Xy(0.0, 0.0);
364 p.set::<0>(3.0);
365 p.set::<1>(4.0);
366 assert_eq!(p.get::<0>(), 3.0);
367 assert_eq!(p.get::<1>(), 4.0);
368 }
369}