geometry_trait/linestring.rs
1//! The [`Linestring`] concept: an ordered sequence of points
2//! (>= 2) with no implicit closing edge.
3//!
4//! Mirrors `doc/concept/linestring.qbk` and the model in
5//! `boost/geometry/geometries/linestring.hpp`. Where the C++ side
6//! exposes the underlying container through `boost::range`
7//! (`begin`/`end` + `range_value`), the Rust port surfaces the
8//! sequence as a `points()` iterator returned via return-position
9//! `impl Trait` in trait (RPITIT) — see proposal §3.6.
10
11use crate::geometry::Geometry;
12use geometry_tag::LinestringTag;
13
14/// A linestring — an ordered sequence of >= 2 points.
15///
16/// Mirrors the Linestring concept (`doc/concept/linestring.qbk`); the
17/// canonical model is `boost::geometry::model::linestring` in
18/// `boost/geometry/geometries/linestring.hpp`, which derives from
19/// `std::vector<Point>` and is consumed through `boost::range`.
20///
21/// The C++ side leaves the iterator type up to each model and reads
22/// it via `boost::range_iterator<G>::type`. The Rust counterpart uses
23/// return-position `impl Trait` in trait so an impl can hand back
24/// whatever iterator its storage produces (`slice::Iter`,
25/// `VecDeque::Iter`, an adapter, ...) without naming it. The
26/// `ExactSizeIterator + Clone` bound mirrors the random-access shape
27/// `boost::range` relies on: callers can ask for `.len()` and
28/// re-iterate without consuming the source.
29///
30/// # Examples
31///
32/// ```
33/// use geometry_trait::Linestring;
34/// fn point_count<L: Linestring>(ls: &L) -> usize { ls.points().len() }
35/// ```
36pub trait Linestring: Geometry<Kind = LinestringTag> {
37 /// The points of this linestring, in declared order.
38 ///
39 /// Plays the role of `boost::begin(ls)` / `boost::end(ls)` from
40 /// `boost/geometry/geometries/linestring.hpp` when read together.
41 fn points(&self) -> impl ExactSizeIterator<Item = &Self::Point> + Clone;
42}
43
44#[cfg(test)]
45mod tests {
46 extern crate alloc;
47
48 use super::*;
49 use crate::point::Point;
50 use alloc::vec;
51 use alloc::vec::Vec;
52 use geometry_cs::Cartesian;
53 use geometry_tag::PointTag;
54
55 struct Xy(f64, f64);
56
57 impl Geometry for Xy {
58 type Kind = PointTag;
59 type Point = Self;
60 }
61
62 impl Point for Xy {
63 type Scalar = f64;
64 type Cs = Cartesian;
65 const DIM: usize = 2;
66
67 fn get<const D: usize>(&self) -> f64 {
68 if D == 0 { self.0 } else { self.1 }
69 }
70 }
71
72 struct VLs(Vec<Xy>);
73
74 impl Geometry for VLs {
75 type Kind = LinestringTag;
76 type Point = Xy;
77 }
78
79 impl Linestring for VLs {
80 fn points(&self) -> impl ExactSizeIterator<Item = &Xy> + Clone {
81 self.0.iter()
82 }
83 }
84
85 #[test]
86 fn linestring_iterates_in_declared_order() {
87 let ls = VLs(vec![Xy(0.0, 0.0), Xy(3.0, 4.0), Xy(4.0, 3.0)]);
88 assert_eq!(ls.points().count(), 3);
89 let xs: Vec<f64> = ls.points().map(Xy::get::<0>).collect();
90 assert_eq!(xs, vec![0.0, 3.0, 4.0]);
91 let ys: Vec<f64> = ls.points().map(Xy::get::<1>).collect();
92 assert_eq!(ys, vec![0.0, 4.0, 3.0]);
93 }
94
95 #[test]
96 fn linestring_iterator_reports_exact_size_and_clones() {
97 let ls = VLs(vec![Xy(0.0, 0.0), Xy(1.0, 1.0)]);
98 let it = ls.points();
99 assert_eq!(it.len(), 2);
100 // Cloning the iterator must not consume the source.
101 let it2 = it.clone();
102 assert_eq!(it.count(), 2);
103 assert_eq!(it2.count(), 2);
104 }
105}