Skip to main content

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}