polysplit/
polysplit.rs

1use std::fmt;
2use std::error;
3use std::cmp::{Ord, Ordering, PartialEq, Eq, PartialOrd};
4use std::collections::BinaryHeap;
5use std::fmt::Debug;
6use std::ops::Add;
7
8/// CutRatioResult presents the closest projection of the point to the segment.
9pub enum CutRatioResult {
10    /// The closest projection is the start of the segment.
11    Begin,
12    /// The closest projection is the point on the segment that splits it in
13    /// the defined proportion (usually `0.0 < ratio < 1.0` where `0.0` is the start
14    /// and `1.0` is the end of the segment).
15    Medium(f64),
16    /// The closest projection is the end of the segment.
17    End,
18}
19
20impl PartialEq for CutRatioResult {
21    fn eq(&self, other: &Self) -> bool {
22        match (self, other) {
23            (Self::Begin, Self::Begin) => true,
24            (Self::Medium(s), Self::Medium(o)) => s.eq(o),
25            (Self::End, Self::End) => true,
26            _ => false,
27        }
28    }
29}
30
31impl Eq for CutRatioResult {}
32
33impl PartialOrd for CutRatioResult {
34    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
35        Some(self.cmp(other))
36    }
37}
38
39impl Ord for CutRatioResult {
40    fn cmp(&self, other: &Self) -> Ordering {
41        match (self, other) {
42            (Self::Begin, Self::Begin) => Ordering::Equal,
43            (Self::Begin, _) => Ordering::Less,
44            (Self::Medium(_), Self::Begin) => Ordering::Greater,
45            (Self::Medium(s), Self::Medium(o)) => s.total_cmp(o),
46            (Self::Medium(_), Self::End) => Ordering::Less,
47            (Self::End, Self::End) => Ordering::Equal,
48            (Self::End, _) => Ordering::Greater,
49        }
50    }
51}
52
53/// DistanceToSegmentResult presents the projection results of the point to the segment.
54pub struct DistanceToSegmentResult<P, D>
55where
56    P: Copy,
57    D: Copy + PartialOrd + Add<Output = D>,
58{
59    pub cut_ratio: CutRatioResult,
60    pub cut_point: P,
61    pub distance: D,
62}
63
64/// PolySplit defines methods for types that can be used in **polyline_split** method.
65pub trait PolySplit<D>
66where
67    Self: Copy,
68    D: Copy + PartialOrd + Add<Output = D>,
69{
70    /// Returns distance to another point.
71    ///
72    /// # Arguments
73    ///
74    /// * `point` - A point distance to should be calculated
75    fn distance_to_point(&self, point: &Self) -> D;
76    /// Returns projection [results](DistanceToSegmentResult) to the segment.
77    ///
78    /// # Arguments
79    ///
80    /// * `segment` - A segment presented by a tuple of points
81    fn distance_to_segment(&self, segment: (&Self, &Self)) -> DistanceToSegmentResult<Self, D>;
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85#[non_exhaustive]
86pub enum PolySplitErrorKind {
87    InvalidPolyline,
88    InvalidPoints,
89    PointFarAway,
90    CannotSplit,
91}
92
93#[derive(Debug)]
94pub struct PolySplitError {
95    pub(super) kind: PolySplitErrorKind,
96    pub message: String,
97}
98
99impl PolySplitError {
100    pub fn kind(&self) -> &PolySplitErrorKind {
101        &self.kind
102    }
103}
104
105impl fmt::Display for PolySplitError {
106    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107        write!(f, "{}", self.message)
108    }
109}
110
111impl error::Error for PolySplitError {}
112
113pub type Result<T> = std::result::Result<T, PolySplitError>;
114
115struct CutPoint<P>
116where P: std::fmt::Debug {
117    segment_index: usize,
118    cut_ratio: CutRatioResult,
119    cut_point: P,
120}
121
122struct Vertex<D> {
123    point_index: usize,
124    cut_point_index: usize,
125    distance_to: D,
126}
127
128#[derive(Copy, Clone, PartialEq)]
129struct State<D> {
130    distance_total: D,
131    position: usize,
132}
133
134impl<D: PartialOrd> Eq for State<D> {
135}
136
137// Heap order depends on `Ord`, so implementing trait to make min-heap instead of max-heap
138impl<D: Copy + PartialOrd> Ord for State<D> {
139    fn cmp(&self, other: &Self) -> Ordering {
140        // Notice that the we flip the ordering on costs.
141        // In case of a tie we compare positions - this step is necessary
142        // to make implementations of `PartialEq` and `Ord` consistent.
143        other.distance_total
144            .partial_cmp(&self.distance_total)
145            .unwrap()
146            .then_with(|| self.position.cmp(&other.position))
147    }
148}
149
150// It is required `PartialOrd` to be also implemented
151impl<D: Copy + PartialOrd> PartialOrd for State<D> {
152    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
153        Some(self.cmp(other))
154    }
155}
156
157/// Splits polyline into segments by the defined list of points.
158///
159/// # Examples
160///
161/// ```
162/// use polysplit::euclidean::Point;
163/// use polysplit::polyline_split;
164///
165/// let polyline = vec![
166///     Point(0.0, 0.0),
167///     Point(10.0, 0.0),
168///     Point(20.0, 0.0),
169/// ];
170/// let points = vec![
171///     Point(1.0, 1.0),
172///     Point(19.0, 1.0),
173/// ];
174///
175/// let segments = polyline_split(&polyline, &points, None).unwrap();
176///
177/// assert_eq!(segments.len(), 1);
178/// println!("{:?}", segments[0]);
179/// ```
180pub fn polyline_split<P, D>(
181    polyline: &[P],
182    points: &[P],
183    distance_threshold: Option<D>,
184) -> Result<Vec<Vec<P>>>
185where
186    P: PolySplit<D> + std::fmt::Debug,
187    D: Copy + PartialOrd + Add<Output = D>,
188{
189    if polyline.len() <= 1 {
190        return Err(PolySplitError{
191            kind: PolySplitErrorKind::InvalidPolyline,
192            message: "polyline has not enough points".to_string(),
193        });
194    }
195
196    if points.len() <= 1 {
197        return Err(PolySplitError{
198            kind: PolySplitErrorKind::InvalidPoints,
199            message: "number of points are not enough".to_string(),
200        });
201    }
202
203    let segments_len = polyline.len() - 1;
204    let points_len = points.len();
205
206    // Collecting all possible cut points
207    let mut cut_points: Vec<CutPoint<P>> = Vec::new();
208
209    for segment_index in 0..segments_len {
210        let segment_a = &polyline[segment_index];
211        let segment_b = &polyline[segment_index + 1];
212
213        let mut is_start_added = false;
214        let mut is_end_added = false;
215
216        for point in points.iter() {
217            let psd: DistanceToSegmentResult<P, D> = point.distance_to_segment((segment_a, segment_b));
218            if let Some(dt) = distance_threshold {
219                if psd.distance > dt {
220                    continue;
221                }
222            }
223
224            match psd.cut_ratio {
225                CutRatioResult::Begin => {
226                    if segment_index == 0 && !is_start_added {
227                        cut_points.push(CutPoint {
228                            segment_index,
229                            cut_ratio: psd.cut_ratio,
230                            cut_point: *segment_a,
231                        });
232
233                        is_start_added = true;
234                    }
235                }
236
237                CutRatioResult::End => {
238                    if !is_end_added {
239                        cut_points.push(CutPoint {
240                            segment_index,
241                            cut_ratio: psd.cut_ratio,
242                            cut_point: *segment_b,
243                        });
244
245                        is_end_added = true;
246                    }
247                },
248
249                _ => {
250                    cut_points.push(CutPoint {
251                        segment_index,
252                        cut_ratio: psd.cut_ratio,
253                        cut_point: psd.cut_point,
254                    });
255                }
256            }
257        }
258    }
259
260    cut_points.sort_unstable_by(|a, b| {
261        match a.segment_index.cmp(&b.segment_index) {
262            Ordering::Equal => a.cut_ratio.partial_cmp(&b.cut_ratio).unwrap(),
263            v => v,
264        }
265    });
266
267    // Building graph
268    let mut vertexes: Vec<Vertex<D>> = Vec::new();
269    let mut edges: Vec<(usize, usize)> = Vec::new();
270
271    let mut last_reachable_cut_point_index = 0;
272
273    for (point_index, point) in points.iter().enumerate() {
274        let start_position = vertexes.len();
275        let mut first_match_cut_point_index = None;
276
277        for (cut_point_index, cut_point) in cut_points.iter().enumerate().skip(last_reachable_cut_point_index) {
278            let distance_to = point.distance_to_point(&cut_point.cut_point);
279            if let Some(dt) = distance_threshold {
280                if distance_to > dt {
281                    continue;
282                }
283            }
284
285            if first_match_cut_point_index.is_none() {
286                first_match_cut_point_index = Some(cut_point_index);
287            }
288
289            vertexes.push(Vertex {
290                point_index,
291                cut_point_index,
292                distance_to,
293            });
294        }
295
296        let end_position = vertexes.len();
297        if start_position == end_position {
298            return Err(PolySplitError{
299                kind: PolySplitErrorKind::PointFarAway,
300                message: "point has no closest segments".to_string(),
301            });
302        }
303
304        edges.push((start_position, end_position));
305        last_reachable_cut_point_index = first_match_cut_point_index.unwrap_or_default();
306    }
307
308    // Initializing start points
309    let vertexes_len = vertexes.len();
310    let mut dist: Vec<Option<D>> = (0..vertexes_len).map(|_| None).collect();
311    let mut prev: Vec<Option<usize>> = (0..vertexes_len).map(|_| None).collect();
312    let mut priority_queue = BinaryHeap::new();
313
314    for idx in edges[0].0..edges[0].1 {
315        let vertex = &vertexes[idx];
316
317        dist[idx] = Some(vertex.distance_to);
318        prev[idx] = None;
319        priority_queue.push(State {
320            distance_total: vertex.distance_to,
321            position: idx,
322        });
323    }
324
325    // Searching for shortest path using Dijkstra's algorithm
326    let mut destination = None;
327    while let Some(State { distance_total, position }) = priority_queue.pop() {
328        let current_vertex = &vertexes[position];
329
330        // Goal is reached
331        if current_vertex.point_index + 1 == points_len {
332            destination = Some(position);
333            break;
334        }
335
336        // Useless state because there is better one
337        if let Some(d) = dist[position] {
338            if distance_total > d {
339                continue;
340            }
341        }
342
343        // Iterating connected vertexes
344        let (from_idx, to_idx) = edges[current_vertex.point_index + 1];
345        for idx in from_idx..to_idx {
346            let neighbour_vertex = &vertexes[idx];
347
348            if current_vertex.cut_point_index > neighbour_vertex.cut_point_index {
349                continue;
350            }
351
352            let relaxed_distance_total = distance_total + neighbour_vertex.distance_to;
353            if dist[idx].map_or(true, |d| d > relaxed_distance_total) {
354                dist[idx] = Some(relaxed_distance_total);
355                prev[idx] = Some(position);
356                priority_queue.push(State {
357                    distance_total: relaxed_distance_total,
358                    position: idx,
359                });
360            }
361        }
362    }
363
364    // Restoring path
365    let mut path = Vec::new();
366    while let Some(idx) = destination {
367        path.push(idx);
368        destination = prev[idx];
369    }
370
371    if path.is_empty() {
372        return Err(PolySplitError{
373            kind: PolySplitErrorKind::CannotSplit,
374            message: "cannot split polyline".to_string(),
375        });
376    }
377
378    path.reverse();
379
380    // Building sub-segments
381    let mut segments: Vec<_> = Vec::with_capacity(segments_len);
382    let mut current_vertex = &vertexes[path[0]];
383    let mut current_cut_point = &cut_points[current_vertex.cut_point_index];
384
385    for next_idx in path[1..].iter() {
386        let next_vertex: &Vertex<D> = &vertexes[*next_idx];
387        let next_cut_point = &cut_points[next_vertex.cut_point_index];
388        let mut segment: Vec<_> = Vec::new();
389
390        if !matches!(current_cut_point.cut_ratio, CutRatioResult::End) {
391            segment.push(current_cut_point.cut_point);
392        }
393
394        for segment_idx in current_cut_point.segment_index..next_cut_point.segment_index {
395            segment.push(polyline[segment_idx + 1]);
396        }
397
398        if !matches!(next_cut_point.cut_ratio, CutRatioResult::Begin) {
399            segment.push(next_cut_point.cut_point);
400        }
401
402        // Two points are matched to same cut point
403        // So adding same point to be valid segment
404        if segment.len() == 1 {
405            segment.push(segment[0]);
406        }
407
408        segments.push(segment);
409        current_vertex = next_vertex;
410        current_cut_point = &cut_points[current_vertex.cut_point_index];
411    }
412
413    Ok(segments)
414}