Skip to content

Commit c3a0af1

Browse files
committed
Add in georust#1050 linesplit too
1 parent b0a02f3 commit c3a0af1

File tree

8 files changed

+1349
-0
lines changed

8 files changed

+1349
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/// The result of splitting a line using
2+
/// [LineSplit::line_split()](crate::algorithm::LineSplit::line_split) method.
3+
/// It can contain between one and two [Lines](crate::Line) / [LineStrings](crate::LineString).
4+
///
5+
/// Note that it may not be desireable to use a `match` statement directly on this type if you only
6+
/// ever want one part of the split. For this please see the helper functions;
7+
/// [.first()](LineSplitResult#method.first),
8+
/// [.second()](LineSplitResult#method.second),
9+
/// [.into_first()](LineSplitResult#method.into_first), and
10+
/// [.into_second()](LineSplitResult#method.into_second).
11+
///
12+
/// ```
13+
/// # use geo::{LineString, coord};
14+
/// # use geo::LineSplit;
15+
///
16+
/// # let my_line = LineString::from(vec![coord! {x: 0., y: 0.},coord! {x: 1., y: 0.},]);
17+
/// if let Some(result) = my_line.line_split_twice(0.2, 0.5) {
18+
/// if let Some(second) = result.into_second() {
19+
/// // got the 'second' part of the split line
20+
/// // between the points 20% and 50% along its length
21+
/// }
22+
/// }
23+
/// ```
24+
#[rustfmt::skip]
25+
#[derive(PartialEq, Debug)]
26+
pub enum LineSplitResult<T> {
27+
First (T ),
28+
Second ( T),
29+
FirstSecond (T, T),
30+
}
31+
32+
#[rustfmt::skip]
33+
impl<T> LineSplitResult<T>{
34+
/// Return only the first of two split line parts, if it exists.
35+
pub fn first(&self) -> Option<&T> {
36+
match self {
37+
Self::First (x ) => Some(x),
38+
Self::Second ( _) => None,
39+
Self::FirstSecond(x, _) => Some(x),
40+
}
41+
}
42+
/// Return only the first of two split line parts, if it exists, consuming the result.
43+
pub fn into_first(self) -> Option<T> {
44+
match self {
45+
Self::First (x ) => Some(x),
46+
Self::Second ( _) => None,
47+
Self::FirstSecond(x, _) => Some(x),
48+
}
49+
}
50+
/// Return only the second of two split line parts, if it exists.
51+
pub fn second(&self) -> Option<&T> {
52+
match self {
53+
Self::First (_ ) => None,
54+
Self::Second ( x) => Some(x),
55+
Self::FirstSecond(_, x) => Some(x),
56+
}
57+
}
58+
/// Return only the second of two split line parts, if it exists, consuming the result.
59+
pub fn into_second(self) -> Option<T> {
60+
match self {
61+
Self::First (_ ) => None,
62+
Self::Second ( x) => Some(x),
63+
Self::FirstSecond(_, x) => Some(x),
64+
}
65+
}
66+
/// Return all two parts of the split line, if they exist.
67+
///
68+
/// Instead of using this, consider using a match statement directly on the
69+
/// [LineSplitResult] type; the reason is that some combinations of this type
70+
/// (eg `(None, None)`) can never exist, but the compiler will still complain about missing arms
71+
/// in your match statement.
72+
pub fn as_tuple(&self) -> (Option<&T>, Option<&T>) {
73+
match self {
74+
Self::First (a ) => (Some(a), None ),
75+
Self::Second ( b) => (None , Some(b)),
76+
Self::FirstSecond(a, b) => (Some(a), Some(b)),
77+
}
78+
}
79+
/// Return all two parts of the split line, if they exist, consuming the result.
80+
///
81+
/// Instead of using this, consider using a match statement directly on the
82+
/// [LineSplitResult] type; the reason is that some combinations of this type
83+
/// (eg `(None, None)`) can never exist, but the compiler will still complain about missing arms
84+
/// in your match statement.
85+
pub fn into_tuple(self) -> (Option<T>, Option<T>) {
86+
match self {
87+
Self::First (a ) => (Some(a), None ),
88+
Self::Second ( b) => (None , Some(b)),
89+
Self::FirstSecond(a, b) => (Some(a), Some(b)),
90+
}
91+
}
92+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
use geo_types::CoordFloat;
2+
3+
use super::{LineSplitResult, LineSplitTwiceResult};
4+
5+
/// Defines functions to split a [Line](crate::Line) or [LineString](crate::LineString)
6+
pub trait LineSplit<Scalar>
7+
where
8+
Self: Sized,
9+
Scalar: CoordFloat,
10+
{
11+
/// Split a [Line](crate::Line) or [LineString](crate::LineString) at some `fraction` of its length.
12+
///
13+
/// `fraction` is any real number. Only values between 0.0 and 1.0 will split the line.
14+
/// Values outside of this range (including infinite values) will be clamped to 0.0 or 1.0.
15+
///
16+
/// Returns `None` when
17+
/// - The provided `fraction` is NAN
18+
/// - The the object being sliced includes NAN or infinite coordinates
19+
///
20+
/// Otherwise returns [`Some(LineSplitResult)`](crate::algorithm::LineSplitResult)
21+
///
22+
/// example
23+
///
24+
/// ```
25+
/// use geo::{Line, coord};
26+
/// use geo::algorithm::{LineSplit, LineSplitResult};
27+
/// let line = Line::new(
28+
/// coord! {x: 0.0, y:0.0},
29+
/// coord! {x:10.0, y:0.0},
30+
/// );
31+
/// let result = line.line_split(0.6);
32+
/// assert_eq!(
33+
/// result,
34+
/// Some(LineSplitResult::FirstSecond(
35+
/// Line::new(
36+
/// coord! {x: 0.0, y:0.0},
37+
/// coord! {x: 6.0, y:0.0},
38+
/// ),
39+
/// Line::new(
40+
/// coord! {x: 6.0, y:0.0},
41+
/// coord! {x:10.0, y:0.0},
42+
/// )
43+
/// ))
44+
/// );
45+
///
46+
/// match result {
47+
/// Some(LineSplitResult::First(line1))=>{},
48+
/// Some(LineSplitResult::Second(line2))=>{},
49+
/// Some(LineSplitResult::FirstSecond(line1, line2))=>{},
50+
/// None=>{},
51+
/// }
52+
/// ```
53+
fn line_split(&self, fraction: Scalar) -> Option<LineSplitResult<Self>>;
54+
55+
///
56+
///
57+
/// example
58+
///
59+
/// ```
60+
///
61+
/// ```
62+
/// > Note: Currently the default implementation of this function provided by the trait is
63+
/// > inefficient because it uses repeated application of the
64+
/// > [.line_split()](LineSplit::line_split) function. In future, types implementing this trait
65+
/// > should override this with a more efficient algorithm if possible.
66+
fn line_split_many(&self, fractions: &Vec<Scalar>) -> Option<Vec<Option<Self>>>
67+
where
68+
Self: Clone,
69+
{
70+
match fractions.len() {
71+
0 => None,
72+
1 => self.line_split(fractions[0]).map(|item| {
73+
let (a, b) = item.into_tuple();
74+
vec![a, b]
75+
}),
76+
_ => {
77+
let mut fractions: Vec<Scalar> = fractions
78+
.iter()
79+
.map(|item| item.min(Scalar::one()).max(Scalar::zero()))
80+
.collect();
81+
fractions
82+
.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
83+
fractions.insert(0, Scalar::zero());
84+
fractions.push(Scalar::one());
85+
let fractions = fractions; // remove mutability
86+
let mut output: Vec<Option<Self>> = Vec::new();
87+
let mut remaining_self = Some(self.clone());
88+
for fraction in fractions.windows(2) {
89+
// cannot be irrefutably unwrapped in for loop *sad crab noises*:
90+
let (a, b) = match fraction {
91+
&[a, b] => (a, b),
92+
_ => return None,
93+
};
94+
let fraction_interval = b - a;
95+
let fraction_to_end = Scalar::one() - a;
96+
let next_fraction = fraction_interval / fraction_to_end;
97+
remaining_self = if let Some(remaining_self) = remaining_self {
98+
match remaining_self.line_split(next_fraction) {
99+
Some(LineSplitResult::FirstSecond(line1, line2)) => {
100+
output.push(Some(line1));
101+
Some(line2)
102+
}
103+
Some(LineSplitResult::First(line1)) => {
104+
output.push(Some(line1));
105+
None
106+
}
107+
Some(LineSplitResult::Second(line2)) => {
108+
output.push(None);
109+
Some(line2)
110+
}
111+
None => return None,
112+
}
113+
} else {
114+
output.push(None);
115+
None
116+
}
117+
}
118+
119+
Some(output)
120+
}
121+
}
122+
}
123+
124+
/// Split a [Line](crate::Line) or [LineString](crate::LineString)
125+
/// at `fraction_start` and at `fraction_end`.
126+
///
127+
/// `fraction_start`/`fraction_end` are any real numbers. Only values between 0.0 and 1.0 will
128+
/// split the line. Values outside of this range (including infinite values) will be clamped to
129+
/// 0.0 or 1.0.
130+
///
131+
/// If `fraction_start > fraction_end`, then the values will be swapped prior splitting.
132+
///
133+
/// Returns [None] when
134+
/// - Either`fraction_start` or `fraction_end` are NAN
135+
/// - The the object being sliced includes NAN or infinite coordinates
136+
///
137+
/// Otherwise Returns a [`Some(LineSplitTwiceResult<T>)`](LineSplitTwiceResult)
138+
///
139+
/// A [`LineSplitTwiceResult<T>`](LineSplitTwiceResult) can contain between one and three
140+
/// line parts where `T` is either [Line](crate::Line) or [LineString](crate::LineString).
141+
///
142+
/// Note that [LineSplitTwiceResult] provides various helper methods to get the desired part(s)
143+
/// of the output.
144+
///
145+
/// The following example shows how to always obtain the "middle" part between the two splits
146+
/// using the [`.into_second()`](LineSplitTwiceResult#method.into_second) method:
147+
///
148+
/// ```
149+
/// use geo::{LineString, line_string};
150+
/// use geo::algorithm::{LineSplit, EuclideanLength};
151+
/// use approx::assert_relative_eq;
152+
/// let my_road_line_string:LineString<f32> = line_string![
153+
/// (x: 0.0,y: 0.0),
154+
/// (x:10.0,y: 0.0),
155+
/// (x:10.0,y:10.0),
156+
/// ];
157+
/// let my_road_len = my_road_line_string.euclidean_length();
158+
/// let fraction_from = 5.0 / my_road_len;
159+
/// let fraction_to = 12.0 / my_road_len;
160+
/// // Extract the road section between `fraction_from` and `fraction_to` using `.into_second()`
161+
/// let my_road_section = match my_road_line_string.line_split_twice(fraction_from, fraction_to) {
162+
/// Some(result) => match result.into_second() { // get the second part of the result
163+
/// Some(linestring)=>Some(linestring),
164+
/// _=>None
165+
/// },
166+
/// _=>None
167+
/// };
168+
/// assert_relative_eq!(my_road_section.unwrap(), line_string![
169+
/// (x: 5.0,y: 0.0),
170+
/// (x:10.0,y: 0.0),
171+
/// (x:10.0,y: 2.0),
172+
/// ]);
173+
/// ```
174+
///
175+
#[rustfmt::skip]
176+
fn line_split_twice(
177+
&self,
178+
fraction_start: Scalar,
179+
fraction_end: Scalar,
180+
) -> Option<LineSplitTwiceResult<Self>> {
181+
// import enum variants
182+
use LineSplitTwiceResult::*;
183+
// reject nan fractions
184+
if fraction_start.is_nan() || fraction_end.is_nan() {
185+
return None;
186+
}
187+
// clamp
188+
let fraction_start = fraction_start.min(Scalar::one()).max(Scalar::zero());
189+
let fraction_end = fraction_end.min(Scalar::one()).max(Scalar::zero());
190+
191+
// swap interval if incorrectly ordered
192+
let (start_fraction, end_fraction) = if fraction_start > fraction_end {
193+
(fraction_end, fraction_start)
194+
} else {
195+
(fraction_start, fraction_end)
196+
};
197+
198+
// find the fraction to split the second portion of the line
199+
let second_fraction =
200+
(end_fraction - start_fraction)
201+
/ (Scalar::one() - start_fraction);
202+
203+
match self.line_split(start_fraction) {
204+
Some(LineSplitResult::FirstSecond(line1, line2)) => match line2.line_split(second_fraction) {
205+
Some(LineSplitResult::FirstSecond(line2, line3)) => Some(FirstSecondThird(line1, line2, line3)),
206+
Some(LineSplitResult::First (line2 )) => Some(FirstSecond (line1, line2 )),
207+
Some(LineSplitResult::Second ( line3)) => Some(FirstThird (line1, line3)),
208+
None => None,
209+
},
210+
Some(LineSplitResult::First (line1)) => Some(First(line1)),
211+
Some(LineSplitResult::Second(line2)) => match line2.line_split(second_fraction) {
212+
Some(LineSplitResult::FirstSecond(line2, line3)) => Some(SecondThird ( line2, line3)),
213+
Some(LineSplitResult::First (line2 )) => Some(Second ( line2 )),
214+
Some(LineSplitResult::Second ( line3)) => Some(Third ( line3)),
215+
None => None,
216+
},
217+
None => None,
218+
}
219+
}
220+
}

0 commit comments

Comments
 (0)