Skip to content

Commit 379775d

Browse files
committed
and georust#935 line offset
1 parent 8536866 commit 379775d

8 files changed

+1320
-0
lines changed

geo/src/algorithm/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ pub use linestring_segment::{LineStringSegmentize, LineStringSegmentizeHaversine
200200
pub mod map_coords;
201201
pub use map_coords::{MapCoords, MapCoordsInPlace};
202202

203+
/// Offset the edges of a geometry perpendicular to the edge direction, either
204+
/// to the left or to the right depending on the sign of the specified distance.
205+
pub mod offset_curve;
206+
pub use offset_curve::OffsetCurve;
207+
203208
/// Orient a `Polygon`'s exterior and interior rings.
204209
pub mod orient;
205210
pub use orient::Orient;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
/// Definitions used in documentation for this module;
2+
///
3+
/// - **line**:
4+
/// - The straight path on a plane that
5+
/// - extends infinitely in both directions.
6+
/// - defined by two distinct points `a` and `b` and
7+
/// - has the direction of the vector from `a` to `b`
8+
///
9+
/// - **segment**:
10+
/// - A finite portion of a `line` which
11+
/// - lies between the points `a` and `b`
12+
/// - has the direction of the vector from `a` to `b`
13+
///
14+
/// - **ray**:
15+
/// - A segment which extends infinitely in the forward direction
16+
///
17+
/// - `Line`: the type [crate::Line] which is actually a **segment**.
18+
19+
use super::vector_extensions::VectorExtensions;
20+
use crate::{
21+
Coord,
22+
CoordFloat,
23+
CoordNum,
24+
// algorithm::kernels::Kernel,
25+
// algorithm::kernels::RobustKernel,
26+
// Orientation
27+
};
28+
29+
/// Used to encode the relationship between a **segment** and an intersection
30+
/// point. See documentation for [LineIntersectionResultWithRelationships]
31+
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
32+
pub(super) enum LineSegmentIntersectionType {
33+
/// The intersection point lies between the start and end of the **segment**
34+
///
35+
/// Abbreviated to `TIP` in original paper
36+
TrueIntersectionPoint,
37+
/// The intersection point is 'false' or 'virtual': it lies on the same
38+
/// **line** as the **segment**, but not between the start and end points of
39+
/// the **segment**.
40+
///
41+
/// Abbreviated to `FIP` in original paper
42+
43+
// Note: Rust does not permit nested enum declaration, so
44+
// FalseIntersectionPointType has to be declared below.
45+
FalseIntersectionPoint(FalseIntersectionPointType),
46+
}
47+
48+
/// These are the variants of [LineSegmentIntersectionType::FalseIntersectionPoint]
49+
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
50+
pub(super) enum FalseIntersectionPointType {
51+
/// The intersection point is 'false' or 'virtual': it lies on the same
52+
/// **line** as the **segment**, and before the start of the **segment**.
53+
///
54+
/// Abbreviated to `NFIP` in original paper (Negative)
55+
/// (also referred to as `FFIP` in Figure 6, but i think this is an
56+
/// error?)
57+
BeforeStart,
58+
/// The intersection point is 'false' or 'virtual': it lies on the same
59+
/// **line** as the **segment**, and after the end of the **segment**.
60+
///
61+
/// Abbreviated to `PFIP` in original paper (Positive)
62+
AfterEnd,
63+
}
64+
65+
66+
/// Struct to contain the result for [line_segment_intersection_with_relationships]
67+
#[derive(Clone, Debug)]
68+
pub(super) struct LineIntersectionResultWithRelationships<T>
69+
where
70+
T: CoordNum,
71+
{
72+
pub ab: LineSegmentIntersectionType,
73+
pub cd: LineSegmentIntersectionType,
74+
pub intersection: Coord<T>,
75+
}
76+
77+
/// Computes the intersection between two line segments;
78+
/// a to b (`ab`), and c to d (`cd`)
79+
///
80+
/// > note: looks like there is already `cartesian_intersect` as a private
81+
/// > method in simplifyvw.rs. It uses the orient2d method of [Kernel],
82+
/// > however it only gives a true/false answer and does not return the
83+
/// > intersection point or parameters needed.
84+
///
85+
/// We already have LineIntersection trait BUT we need a function that also
86+
/// returns the parameters for both lines described below. The LineIntersection
87+
/// trait uses some fancy unrolled code it seems unlikely it could be adapted
88+
/// for this purpose.
89+
///
90+
/// Returns the intersection point **and** parameters `t_ab` and `t_cd`
91+
/// described below
92+
///
93+
/// The intersection of segments can be expressed as a parametric equation
94+
/// where `t_ab` and `t_cd` are unknown scalars :
95+
///
96+
/// ```text
97+
/// a + ab · t_ab = c + cd · t_cd
98+
/// ```
99+
///
100+
/// > note: a real intersection can only happen when `0 <= t_ab <= 1` and
101+
/// > `0 <= t_cd <= 1` but this function will find intersections anyway
102+
/// > which may lay outside of the line segments
103+
///
104+
/// This can be rearranged as follows:
105+
///
106+
/// ```text
107+
/// ab · t_ab - cd · t_cd = c - a
108+
/// ```
109+
///
110+
/// Collecting the scalars `t_ab` and `-t_cd` into the column vector `T`,
111+
/// and by collecting the vectors `ab` and `cd` into matrix `M`:
112+
/// we get the matrix form:
113+
///
114+
/// ```text
115+
/// [ab_x cd_x][ t_ab] = [ac_x]
116+
/// [ab_y cd_y][-t_cd] [ac_y]
117+
/// ```
118+
///
119+
/// or
120+
///
121+
/// ```text
122+
/// M·T=ac
123+
/// ```
124+
///
125+
/// Inverting the matrix `M` involves taking the reciprocal of the determinant
126+
/// (the determinant is same as the of the [cross_product()] of `ab` and `cd`)
127+
///
128+
/// ```text
129+
/// 1/(ab×cd)
130+
/// ```
131+
///
132+
/// Therefore if `ab×cd = 0` the determinant is undefined and the matrix cannot
133+
/// be inverted. The lines are either
134+
/// a) parallel or
135+
/// b) collinear
136+
///
137+
/// Pre-multiplying both sides by the inverted 2x2 matrix we get:
138+
///
139+
/// ```text
140+
/// [ t_ab] = 1/(ab×cd) · [ cd_y -cd_x][ac_x]
141+
/// [-t_cd] [-ab_y ab_x][ac_y]
142+
/// ```
143+
///
144+
/// or
145+
///
146+
/// ```text
147+
/// T = M⁻¹·ac
148+
/// ```
149+
///
150+
/// Expands to:
151+
///
152+
/// ```text
153+
/// [ t_ab] = 1/(ab_x·cd_y - ab_y·cd_x)·[ cd_y·ac_x - cd_x·ac_y]
154+
/// [-t_cd] [-ab_y·ac_x + ab_x·ac_y]
155+
/// ```
156+
///
157+
/// Since it is tidier to write cross products, observe that the above is
158+
/// equivalent to:
159+
///
160+
/// ```text
161+
/// [t_ab] = [ ac×cd / ab×cd ]
162+
/// [t_cd] = [ - ab×ac / ab×cd ]
163+
/// ```
164+
165+
fn line_segment_intersection_with_parameters<T>(
166+
a: Coord<T>,
167+
b: Coord<T>,
168+
c: Coord<T>,
169+
d: Coord<T>,
170+
) -> Option<(T, T, Coord<T>)>
171+
where
172+
T: CoordFloat,
173+
{
174+
let ab = b - a;
175+
let cd = d - c;
176+
let ac = c - a;
177+
178+
let ab_cross_cd = ab.cross_product_2d(cd);
179+
if T::is_zero(&ab_cross_cd) {
180+
// Segments are exactly parallel or colinear
181+
None
182+
} else {
183+
// Division by zero is prevented, but testing is needed to see what
184+
// happens for near-parallel sections of line.
185+
let t_ab = ac.cross_product_2d(cd) / ab_cross_cd;
186+
let t_cd = -ab.cross_product_2d(ac) / ab_cross_cd;
187+
let intersection = a + ab * t_ab;
188+
189+
Some((t_ab, t_cd, intersection))
190+
}
191+
192+
// TODO:
193+
// The above could be replaced with the following, but at the cost of
194+
// repeating some computation.
195+
196+
// match RobustKernel::orient2d(*a, *b, *d) {
197+
// Orientation::Collinear => None,
198+
// _ => {
199+
// let t_ab = cross_product_2d(ac, cd) / ab_cross_cd;
200+
// let t_cd = -cross_product_2d(ab, ac) / ab_cross_cd;
201+
// let intersection = *a + ab * t_ab;
202+
// Some((t_ab, t_cd, intersection))
203+
// }
204+
// }
205+
}
206+
207+
/// This is a simple wrapper for [line_segment_intersection_with_parameters];
208+
/// Returns the intersection point as well as the relationship between the point
209+
/// and each of the input line segments. See [LineSegmentIntersectionType]
210+
pub(super) fn line_segment_intersection_with_relationships<T>(
211+
a: Coord<T>,
212+
b: Coord<T>,
213+
c: Coord<T>,
214+
d: Coord<T>,
215+
) -> Option<LineIntersectionResultWithRelationships<T>>
216+
where
217+
T: CoordFloat,
218+
{
219+
line_segment_intersection_with_parameters(a, b, c, d).map(|(t_ab, t_cd, intersection)| {
220+
use FalseIntersectionPointType::{AfterEnd, BeforeStart};
221+
use LineSegmentIntersectionType::{FalseIntersectionPoint, TrueIntersectionPoint};
222+
LineIntersectionResultWithRelationships {
223+
ab: if T::zero() <= t_ab && t_ab <= T::one() {
224+
TrueIntersectionPoint
225+
} else if t_ab < T::zero() {
226+
FalseIntersectionPoint(BeforeStart)
227+
} else {
228+
FalseIntersectionPoint(AfterEnd)
229+
},
230+
cd: if T::zero() <= t_cd && t_cd <= T::one() {
231+
TrueIntersectionPoint
232+
} else if t_cd < T::zero() {
233+
FalseIntersectionPoint(BeforeStart)
234+
} else {
235+
FalseIntersectionPoint(AfterEnd)
236+
},
237+
intersection,
238+
}
239+
})
240+
}
241+
242+
#[cfg(test)]
243+
mod test {
244+
use super::{
245+
line_segment_intersection_with_parameters, line_segment_intersection_with_relationships,
246+
FalseIntersectionPointType, LineIntersectionResultWithRelationships,
247+
LineSegmentIntersectionType,
248+
};
249+
use crate::{Coord};
250+
use FalseIntersectionPointType::{AfterEnd, BeforeStart};
251+
use LineSegmentIntersectionType::{FalseIntersectionPoint, TrueIntersectionPoint};
252+
253+
#[test]
254+
fn test_line_segment_intersection_with_parameters() {
255+
let a = Coord { x: 0f64, y: 0f64 };
256+
let b = Coord { x: 2f64, y: 2f64 };
257+
let c = Coord { x: 0f64, y: 1f64 };
258+
let d = Coord { x: 1f64, y: 0f64 };
259+
if let Some((t_ab, t_cd, intersection)) =
260+
line_segment_intersection_with_parameters(a, b, c, d)
261+
{
262+
assert_eq!(t_ab, 0.25f64);
263+
assert_eq!(t_cd, 0.50f64);
264+
assert_eq!(
265+
intersection,
266+
Coord {
267+
x: 0.5f64,
268+
y: 0.5f64
269+
}
270+
);
271+
} else {
272+
assert!(false)
273+
}
274+
}
275+
276+
#[test]
277+
fn test_line_segment_intersection_with_parameters_parallel() {
278+
let a = Coord { x: 3f64, y: 4f64 };
279+
let b = Coord { x: 6f64, y: 8f64 };
280+
let c = Coord { x: 9f64, y: 9f64 };
281+
let d = Coord { x: 12f64, y: 13f64 };
282+
assert_eq!(
283+
line_segment_intersection_with_parameters(a, b, c, d),
284+
None
285+
)
286+
}
287+
#[test]
288+
fn test_line_segment_intersection_with_parameters_colinear() {
289+
let a = Coord { x: 1f64, y: 2f64 };
290+
let b = Coord { x: 2f64, y: 4f64 };
291+
let c = Coord { x: 3f64, y: 6f64 };
292+
let d = Coord { x: 5f64, y: 10f64 };
293+
assert_eq!(
294+
line_segment_intersection_with_parameters(a, b, c, d),
295+
None
296+
)
297+
}
298+
299+
#[test]
300+
fn test_line_segment_intersection_with_relationships() {
301+
let a = Coord { x: 1f64, y: 2f64 };
302+
let b = Coord { x: 2f64, y: 3f64 };
303+
let c = Coord { x: 0f64, y: 2f64 };
304+
let d = Coord { x: -2f64, y: 6f64 };
305+
306+
let expected_intersection_point = Coord { x: 1f64 / 3f64, y: 4f64 / 3f64 };
307+
308+
if let Some(LineIntersectionResultWithRelationships {
309+
ab,
310+
cd,
311+
intersection,
312+
}) = line_segment_intersection_with_relationships(a, b, c, d)
313+
{
314+
assert_eq!(ab, FalseIntersectionPoint(BeforeStart));
315+
assert_eq!(cd, FalseIntersectionPoint(BeforeStart));
316+
assert_relative_eq!(intersection, expected_intersection_point);
317+
} else {
318+
assert!(false);
319+
}
320+
321+
if let Some(LineIntersectionResultWithRelationships {
322+
ab,
323+
cd,
324+
intersection,
325+
}) = line_segment_intersection_with_relationships(b, a, c, d)
326+
{
327+
assert_eq!(ab, FalseIntersectionPoint(AfterEnd));
328+
assert_eq!(cd, FalseIntersectionPoint(BeforeStart));
329+
assert_relative_eq!(intersection, expected_intersection_point);
330+
} else {
331+
assert!(false);
332+
}
333+
334+
if let Some(LineIntersectionResultWithRelationships {
335+
ab,
336+
cd,
337+
intersection,
338+
}) = line_segment_intersection_with_relationships(a, b, d, c)
339+
{
340+
assert_eq!(ab, FalseIntersectionPoint(BeforeStart));
341+
assert_eq!(cd, FalseIntersectionPoint(AfterEnd));
342+
assert_relative_eq!(intersection, expected_intersection_point);
343+
} else {
344+
assert!(false);
345+
}
346+
}
347+
348+
#[test]
349+
fn test_line_segment_intersection_with_relationships_true() {
350+
let a = Coord { x: 0f64, y: 1f64 };
351+
let b = Coord { x: 2f64, y: 3f64 };
352+
let c = Coord { x: 0f64, y: 2f64 };
353+
let d = Coord { x: -2f64, y: 6f64 };
354+
355+
let expected_intersection_point = Coord { x: 1f64 / 3f64, y: 4f64 / 3f64 };
356+
357+
if let Some(LineIntersectionResultWithRelationships {
358+
ab,
359+
cd,
360+
intersection,
361+
}) = line_segment_intersection_with_relationships(a, b, c, d)
362+
{
363+
assert_eq!(ab, TrueIntersectionPoint);
364+
assert_eq!(cd, FalseIntersectionPoint(BeforeStart));
365+
assert_relative_eq!(intersection, expected_intersection_point);
366+
} else {
367+
assert!(false);
368+
}
369+
}
370+
}

0 commit comments

Comments
 (0)