iron_shapes/
point_string.rs

1// Copyright (c) 2018-2020 Thomas Kramer.
2// SPDX-FileCopyrightText: 2018-2022 Thomas Kramer
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6//! A point string is a finite sequence of points.
7
8use crate::edge::Edge;
9use crate::point::Point;
10use crate::rect::Rect;
11
12use crate::CoordinateType;
13
14pub use crate::traits::TryBoundingBox;
15use crate::traits::{MapPointwise, TryCastCoord};
16
17use std::iter::FromIterator;
18use std::slice::Iter;
19
20use num_traits::{Float, NumCast};
21use std::ops::Sub;
22
23/// A point string is a finite sequence of points.
24/// TODO: Implement `Deref` for accessing the list of points.
25#[derive(Clone, Debug, PartialEq, Eq, Hash)]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27pub struct PointString<T> {
28    /// The points defining this point string.
29    pub points: Vec<Point<T>>,
30}
31
32impl<T> PointString<T> {
33    /// Get the number of vertices.
34    pub fn len(&self) -> usize {
35        self.points.len()
36    }
37
38    /// Check if string has zero length.
39    pub fn is_empty(&self) -> bool {
40        self.points.is_empty()
41    }
42}
43
44impl<T: Copy> PointString<T> {
45    /// Create new point string by taking vertices from a type that implements `Into<PointString<T>>`.
46    pub fn new<I>(i: I) -> Self
47    where
48        I: Into<Self>,
49    {
50        i.into()
51    }
52
53    /// Shortcut for `self.points.iter()`.
54    pub fn iter(&self) -> Iter<Point<T>> {
55        self.points.iter()
56    }
57
58    /// Get the sequence of edges of the point string starting from the first point to the last.
59    /// # Examples
60    ///
61    /// ```
62    /// use iron_shapes::point_string::PointString;
63    /// use iron_shapes::edge::Edge;
64    /// let coords = vec![(0, 0), (1, 0), (2, 0)];
65    ///
66    /// let point_string = PointString::new(coords);
67    ///
68    /// let edges: Vec<_> = point_string.edges().collect();
69    ///
70    /// assert_eq!(edges, vec![Edge::new((0, 0), (1, 0)), Edge::new((1, 0), (2, 0))]);
71    /// ```
72    pub fn edges(&self) -> impl Iterator<Item = Edge<T>> + '_ {
73        self.iter()
74            .zip(self.iter().skip(1))
75            .map(|(a, b)| Edge::new(a, b))
76    }
77
78    /// Same as `edges` but in reverse order.
79    /// Get the sequence of edges of the point string starting from the last point to the first.
80    /// # Examples
81    ///
82    /// ```
83    /// use iron_shapes::point_string::PointString;
84    /// use iron_shapes::edge::Edge;
85    /// let coords = vec![(0, 0), (1, 0), (2, 0)];
86    ///
87    /// let point_string = PointString::new(coords);
88    ///
89    /// let edges: Vec<_> = point_string.edges_reversed().collect();
90    ///
91    /// assert_eq!(edges, vec![Edge::new((2, 0), (1, 0)), Edge::new((1, 0), (0, 0))]);
92    /// ```
93    pub fn edges_reversed(&self) -> impl Iterator<Item = Edge<T>> + '_ {
94        self.iter()
95            .rev()
96            .zip(self.iter().rev().skip(1))
97            .map(|(a, b)| Edge::new(a, b))
98    }
99}
100
101impl<T: Copy + Sub<Output = T> + NumCast> PointString<T> {
102    /// Compute geometrical length of the path defined by the point string.
103    /// # Examples
104    ///
105    /// ```
106    /// use iron_shapes::point_string::PointString;
107    /// let coords = vec![(0, 0), (1, 0), (2, 0)];
108    ///
109    /// let point_string = PointString::new(coords);
110    ///
111    /// assert_eq!(point_string.path_length::<f64>(), 2.0);
112    /// ```
113    pub fn path_length<F: Float>(&self) -> F {
114        self.iter()
115            .zip(self.iter().skip(1))
116            .map(|(a, b)| a.distance(b))
117            .fold(F::zero(), |a, b| a + b)
118    }
119}
120
121impl<T: Copy + NumCast, Dst: CoordinateType + NumCast> TryCastCoord<T, Dst> for PointString<T> {
122    type Output = PointString<Dst>;
123
124    fn try_cast(&self) -> Option<Self::Output> {
125        let new_points: Option<Vec<_>> = self.points.iter().map(|p| p.try_cast()).collect();
126
127        new_points.map(|p| PointString::new(p))
128    }
129}
130
131/// Create a point string from something that can be turned into an iterator of values convertible to [`Point`]s.
132impl<I, T, P> From<I> for PointString<T>
133where
134    T: Copy,
135    I: IntoIterator<Item = P>,
136    Point<T>: From<P>,
137{
138    fn from(iter: I) -> Self {
139        let points: Vec<Point<T>> = iter.into_iter().map(|x| x.into()).collect();
140
141        PointString { points }
142    }
143}
144//
145// /// Create a point string from a [`Vec`] of values convertible to [`Point`]s.
146// impl<'a, T, P> From<&'a Vec<P>> for PointString<T>
147//     where T: CoordinateType,
148//           Point<T>: From<&'a P>
149// {
150//     fn from(vec: &'a Vec<P>) -> Self {
151//         let points: Vec<Point<T>> = vec.into_iter().map(
152//             |x| x.into()
153//         ).collect();
154//
155//         PointString { points }
156//     }
157// }
158//
159// /// Create a point string from a [`Vec`] of values convertible to [`Point`]s.
160// impl<T, P> From<Vec<P>> for PointString<T>
161//     where T: CoordinateType,
162//           Point<T>: From<P>
163// {
164//     fn from(vec: Vec<P>) -> Self {
165//         let points: Vec<Point<T>> = vec.into_iter().map(
166//             |x| x.into()
167//         ).collect();
168//
169//         PointString { points }
170//     }
171// }
172//
173
174/// Create a point string from a iterator of values convertible to [`Point`]s.
175impl<T, P> FromIterator<P> for PointString<T>
176where
177    T: Copy,
178    P: Into<Point<T>>,
179{
180    fn from_iter<I>(iter: I) -> Self
181    where
182        I: IntoIterator<Item = P>,
183    {
184        let points: Vec<Point<T>> = iter.into_iter().map(|x| x.into()).collect();
185
186        PointString { points }
187    }
188}
189
190impl<T> MapPointwise<T> for PointString<T>
191where
192    T: Copy,
193{
194    fn transform<F: Fn(Point<T>) -> Point<T>>(&self, tf: F) -> Self {
195        let points = self.points.iter().map(|&p| tf(p)).collect();
196
197        PointString { points }
198    }
199}
200
201impl<T> TryBoundingBox<T> for PointString<T>
202where
203    T: Copy + PartialOrd,
204{
205    /// Compute the bounding box of all the points in this string.
206    /// Returns `None` if the string is empty.
207    /// # Examples
208    ///
209    /// ```
210    /// use iron_shapes::point_string::PointString;
211    /// use iron_shapes::traits::TryBoundingBox;
212    /// use iron_shapes::rect::Rect;
213    /// let coords = vec![(0, 0), (1, 0), (2, 1), (-1, -3)];
214    ///
215    /// let point_string = PointString::new(coords);
216    ///
217    /// assert_eq!(point_string.try_bounding_box(), Some(Rect::new((2, 1), (-1, -3))));
218    /// ```
219    fn try_bounding_box(&self) -> Option<Rect<T>> {
220        if self.points.is_empty() {
221            None
222        } else {
223            let mut x_min = self.points[0].x;
224            let mut x_max = x_min;
225            let mut y_min = self.points[0].y;
226            let mut y_max = y_min;
227
228            for p in self.iter().skip(1) {
229                let (x, y) = p.into();
230                if x < x_min {
231                    x_min = x;
232                }
233                if x > x_max {
234                    x_max = x;
235                }
236                if y < y_min {
237                    y_min = y;
238                }
239                if y > y_max {
240                    y_max = y;
241                }
242            }
243
244            Some(Rect::new((x_min, y_min), (x_max, y_max)))
245        }
246    }
247}