1use std::{cmp::Ordering, ops::Deref};
2
3use super::SweepPoint;
4use crate::{
5 line_intersection::line_intersection, Coord, GeoFloat, GeoNum, Kernel, Line, LineIntersection,
6};
7
8#[derive(Clone, Copy)]
14pub struct LineOrPoint<T: GeoNum> {
15 left: SweepPoint<T>,
16 right: SweepPoint<T>,
17}
18
19impl<T: GeoNum> std::fmt::Debug for LineOrPoint<T> {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 f.debug_tuple(if self.is_line() { "LPt" } else { "Pt" })
22 .field(&self.left.x_y())
23 .field(&self.right.x_y())
24 .finish()
25 }
26}
27
28impl<T: GeoNum> From<SweepPoint<T>> for LineOrPoint<T> {
29 fn from(pt: SweepPoint<T>) -> Self {
30 Self {
31 left: pt,
32 right: pt,
33 }
34 }
35}
36
37impl<T: GeoNum> From<(SweepPoint<T>, SweepPoint<T>)> for LineOrPoint<T> {
38 fn from(pt: (SweepPoint<T>, SweepPoint<T>)) -> Self {
39 let (start, end) = pt;
40 match start.cmp(&end) {
41 Ordering::Less => Self {
42 left: start,
43 right: end,
44 },
45 _ => Self {
46 left: end,
47 right: start,
48 },
49 }
50 }
51}
52
53impl<T: GeoNum> From<Line<T>> for LineOrPoint<T> {
55 fn from(l: Line<T>) -> Self {
56 let start: SweepPoint<T> = l.start.into();
57 let end = l.end.into();
58 (start, end).into()
59 }
60}
61
62impl<T: GeoNum> From<Coord<T>> for LineOrPoint<T> {
64 fn from(c: Coord<T>) -> Self {
65 Self {
66 left: c.into(),
67 right: c.into(),
68 }
69 }
70}
71
72impl<T: GeoNum> LineOrPoint<T> {
73 #[inline]
75 pub fn is_line(&self) -> bool {
76 self.left != self.right
77 }
78
79 #[inline]
81 pub fn line(&self) -> Line<T> {
82 Line::new(*self.left, *self.right)
83 }
84
85 #[inline]
86 pub fn left(&self) -> SweepPoint<T> {
87 self.left
88 }
89
90 #[inline]
91 pub fn right(&self) -> SweepPoint<T> {
92 self.right
93 }
94
95 #[cfg(test)]
96 pub fn coords_equal(&self, other: &LineOrPoint<T>) -> bool {
97 self.is_line() == other.is_line() && self.end_points() == other.end_points()
98 }
99
100 #[inline]
101 pub fn end_points(&self) -> (SweepPoint<T>, SweepPoint<T>) {
102 (self.left, self.right)
103 }
104
105 pub fn new(left: SweepPoint<T>, right: SweepPoint<T>) -> Self {
106 Self { left, right }
107 }
108}
109
110impl<T: GeoNum> PartialEq for LineOrPoint<T> {
112 #[inline]
113 fn eq(&self, other: &Self) -> bool {
114 self.partial_cmp(other) == Some(Ordering::Equal)
115 }
116}
117
118impl<T: GeoNum> PartialOrd for LineOrPoint<T> {
128 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
129 match (self.is_line(), other.is_line()) {
130 (false, false) => {
131 if self.left == other.left {
132 Some(Ordering::Equal)
133 } else {
134 None
137 }
138 }
139 (false, true) => other.partial_cmp(self).map(Ordering::reverse),
140 (true, false) => {
141 let (p, q) = self.end_points();
142 let r = other.left;
143 if r > q || p > r {
144 return None;
145 }
146 Some(
147 T::Ker::orient2d(*p, *q, *r)
148 .as_ordering()
149 .then(Ordering::Greater),
150 )
151 }
152 (true, true) => {
153 let (p1, q1) = self.end_points();
154 let (p2, q2) = other.end_points();
155 if p1 > p2 {
156 return other.partial_cmp(self).map(Ordering::reverse);
157 }
158 if p1 >= q2 || p2 >= q1 {
159 return None;
160 }
161
162 Some(
165 T::Ker::orient2d(*p1, *q1, *p2)
166 .as_ordering()
167 .then_with(|| T::Ker::orient2d(*p1, *q1, *q2).as_ordering()),
168 )
169 }
170 }
171 }
172}
173
174impl<T: GeoFloat> LineOrPoint<T> {
175 pub fn intersect_line(&self, other: &Self) -> Option<Self> {
179 debug_assert!(other.is_line(), "tried to intersect with a point variant!");
180
181 let line = other.line();
182 if !self.is_line() {
183 let p = self.left;
184 use crate::Intersects;
185 if line.intersects(&*p) {
186 Some(*self)
187 } else {
188 None
189 }
190 } else {
191 line_intersection(self.line(), line).map(|l| match l {
192 LineIntersection::SinglePoint {
193 intersection,
194 is_proper,
195 } => {
196 let mut pt = intersection;
197 if is_proper && (&pt == self.left.deref()) {
198 if self.left.x == self.right.x {
199 pt.y = pt.y.next_after(T::infinity());
200 } else {
201 pt.x = pt.x.next_after(T::infinity());
202 }
203 }
204 pt.into()
205 }
206 LineIntersection::Collinear { intersection } => intersection.into(),
207 })
208 }
209 }
210
211 pub fn intersect_line_ordered(&self, other: &Self) -> Option<Self> {
212 let ord = self.partial_cmp(other);
213 match self.intersect_line(other) {
214 Some(lp) if !lp.is_line() => {
215 let pt = lp.left;
242 let (mut x, y) = pt.x_y();
243
244 let c = self.left;
245 if x == c.x && y < c.y {
246 x = x.next_after(T::infinity());
247 }
248
249 let pt: SweepPoint<_> = Coord { x, y }.into();
250 debug_assert!(
251 pt >= self.left,
252 "line intersection before first line: {pt:?}\n\tLine({lp1:?} - {lp2:?}) X Line({lp3:?} - {lp4:?})",
253 lp1 = self.left,
254 lp2 = self.right,
255 lp3 = other.left,
256 lp4 = other.right,
257 );
258 debug_assert!(
259 pt >= other.left,
260 "line intersection before second line: {pt:?}\n\tLine({lp1:?} - {lp2:?}) X Line({lp3:?} - {lp4:?})",
261 lp1 = self.left,
262 lp2 = self.right,
263 lp3 = other.left,
264 lp4 = other.right,
265 );
266
267 if let Some(ord) = ord {
268 let l1 = LineOrPoint::from((self.left, pt));
269 let l2 = LineOrPoint {
270 left: other.left,
271 right: pt,
272 };
273 let cmp = l1.partial_cmp(&l2).unwrap();
274 if l1.is_line() && l2.is_line() && cmp.then(ord) != ord {
275 debug!(
276 "ordering changed by intersection: {l1:?} {ord:?} {l2:?}",
277 l1 = self,
278 l2 = other
279 );
280 debug!("\tparts: {l1:?}, {l2:?}");
281 debug!("\tintersection: {pt:?} {cmp:?}");
282
283 return Some(if self.left > other.left {
286 self.left.into()
287 } else {
288 other.left.into()
289 });
290 }
291 }
292 Some((*pt).into())
293 }
294 e => e,
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use std::cmp::Ordering;
302
303 use geo_types::{Coord, LineString};
304 use wkt::ToWkt;
305
306 use crate::{GeoFloat, GeoNum, HasKernel, Kernel};
307
308 use super::LineOrPoint;
309
310 #[test]
312 #[ignore]
313 fn check_ordering() {
314 let pt_7 = Coord::from((-32.57812499999999, 241.33427773853316));
315 let pt_8 = Coord::from((-36.11348070978957, 237.7989220287436));
316 let pt_13 = Coord::from((-25.507080078124993, 248.40532266040816));
317 let pt_14 = Coord::from((-36.48784219165816, 237.424560546875));
318 let _pt_15 = Coord::from((4.4929199218750036, 196.44379843334184));
319 let pt_16 = Coord::from((-36.048578439260666, 237.8638242992725));
320 let pt_17 = Coord::from((3.545624214480127, 197.39109414073673));
321
322 fn check_isection<T: GeoFloat>(abcd: [Coord<T>; 4]) -> Option<LineOrPoint<T>> {
323 let l1 = LineOrPoint::from((abcd[0].into(), abcd[1].into()));
324 let l2 = LineOrPoint::from((abcd[2].into(), abcd[3].into()));
325 l1.intersect_line_ordered(&l2)
326 }
327 fn check_lines<T: GeoNum>(abcd: [Coord<T>; 4]) -> Ordering {
328 let l1 = LineOrPoint::from((abcd[0].into(), abcd[1].into()));
329 let l2 = LineOrPoint::from((abcd[2].into(), abcd[3].into()));
330 l1.partial_cmp(&l2).unwrap()
331 }
332
333 eprintln!(
334 "(14-17) {cmp:?} (14-16)",
335 cmp = check_lines([pt_14, pt_17, pt_14, pt_16])
336 );
337 eprintln!(
338 "(8-16) {cmp:?} (14-16)",
339 cmp = check_lines([pt_8, pt_16, pt_14, pt_16]),
340 );
341 eprintln!(
342 "(8-7) {cmp:?} (14-16)",
343 cmp = check_lines([pt_8, pt_7, pt_14, pt_16]),
344 );
345 eprintln!(
346 "(8-7) {cmp:?} (14-13)",
347 cmp = check_lines([pt_8, pt_7, pt_14, pt_13]),
348 );
349 eprintln!(
350 "(8-7) {isect:?} (14-13)",
351 isect = check_isection([pt_8, pt_7, pt_14, pt_13]),
352 );
353 let l87 = LineString::new(vec![pt_8, pt_16, pt_7]);
354 let lo = LineString::new(vec![pt_14, pt_16, pt_13]);
355 eprintln!("l1: {}", l87.to_wkt());
356 eprintln!("lo: {}", lo.to_wkt());
357
358 eprintln!(
359 "pred: {:?}",
360 <f64 as HasKernel>::Ker::orient2d(pt_8, pt_7, pt_17)
361 );
362 eprintln!(
363 "pred: {:?}",
364 <f64 as HasKernel>::Ker::orient2d(pt_8, pt_14, pt_16)
365 );
366 }
367}