tg_geom/
line.rs

1//! Line string type representing a sequence of connected line segments.
2//!
3//! A line string is an ordered sequence of points connected by straight
4//! segments. Unlike a [`Ring`](crate::Ring), a line string is not required
5//! to be closed (first point ≠ last point).
6
7use core::marker::PhantomData;
8use core::ptr::NonNull;
9
10use crate::error::{Error, Result};
11use crate::{IndexKind, Point, Rect, Segment};
12
13macro_rules! impl_line_methods {
14    ($get_ptr:expr) => {
15        #[inline]
16        pub fn rect(&self) -> Rect {
17            let ptr = $get_ptr(self);
18            let r = unsafe { tg_geom_sys::tg_line_rect(ptr) };
19            r.into()
20        }
21
22        #[inline]
23        pub fn num_points(&self) -> usize {
24            let ptr = $get_ptr(self);
25            unsafe { tg_geom_sys::tg_line_num_points(ptr) as usize }
26        }
27
28        #[inline]
29        pub fn point_at(&self, index: usize) -> Option<Point> {
30            if index >= self.num_points() {
31                None
32            } else {
33                let ptr = $get_ptr(self);
34                let p = unsafe { tg_geom_sys::tg_line_point_at(ptr, index as libc::c_int) };
35                Some(p.into())
36            }
37        }
38
39        #[inline]
40        pub fn points(&self) -> &[Point] {
41            let ptr = $get_ptr(self);
42            let raw = unsafe { tg_geom_sys::tg_line_points(ptr) };
43            if raw.is_null() {
44                &[]
45            } else {
46                unsafe { core::slice::from_raw_parts(raw as *const Point, self.num_points()) }
47            }
48        }
49
50        #[inline]
51        pub fn num_segments(&self) -> usize {
52            let ptr = $get_ptr(self);
53            unsafe { tg_geom_sys::tg_line_num_segments(ptr) as usize }
54        }
55
56        #[inline]
57        pub fn segment_at(&self, index: usize) -> Option<Segment> {
58            if index >= self.num_segments() {
59                None
60            } else {
61                let ptr = $get_ptr(self);
62                let s = unsafe { tg_geom_sys::tg_line_segment_at(ptr, index as libc::c_int) };
63                Some(s.into())
64            }
65        }
66
67        #[inline]
68        pub fn clockwise(&self) -> bool {
69            let ptr = $get_ptr(self);
70            unsafe { tg_geom_sys::tg_line_clockwise(ptr) }
71        }
72
73        #[inline]
74        pub fn length(&self) -> f64 {
75            let ptr = $get_ptr(self);
76            unsafe { tg_geom_sys::tg_line_length(ptr) }
77        }
78    };
79}
80
81/// An owned line string.
82pub struct Line {
83    ptr: NonNull<tg_geom_sys::tg_line>,
84}
85
86impl Line {
87    impl_line_methods!(|s: &Self| s.ptr.as_ptr());
88
89    crate::utils::impl_nearest_segment!(tg_geom_sys::tg_line_nearest_segment);
90
91    pub fn new(points: &[Point]) -> Result<Self> {
92        Self::new_ix(points, IndexKind::Default)
93    }
94
95    pub fn new_ix(points: &[Point], ix: IndexKind) -> Result<Self> {
96        let ptr = unsafe {
97            tg_geom_sys::tg_line_new_ix(
98                points.as_ptr() as *const tg_geom_sys::tg_point,
99                points.len() as libc::c_int,
100                ix.into(),
101            )
102        };
103        NonNull::new(ptr)
104            .map(|ptr| Self { ptr })
105            .ok_or(Error::OutOfMemory)
106    }
107
108    /// # Safety
109    /// The pointer must be valid and not owned elsewhere.
110    pub unsafe fn from_raw(ptr: *mut tg_geom_sys::tg_line) -> Option<Self> {
111        NonNull::new(ptr).map(|ptr| Self { ptr })
112    }
113
114    #[inline]
115    pub fn as_ptr(&self) -> *const tg_geom_sys::tg_line {
116        self.ptr.as_ptr()
117    }
118
119    pub fn into_raw(self) -> *mut tg_geom_sys::tg_line {
120        let ptr = self.ptr.as_ptr();
121        core::mem::forget(self);
122        ptr
123    }
124
125    pub fn copy(&self) -> Result<Self> {
126        let ptr = unsafe { tg_geom_sys::tg_line_copy(self.ptr.as_ptr()) };
127        NonNull::new(ptr)
128            .map(|ptr| Self { ptr })
129            .ok_or(Error::CopyFailed)
130    }
131
132    pub fn clone_ref(&self) -> Result<Self> {
133        let ptr = unsafe { tg_geom_sys::tg_line_clone(self.ptr.as_ptr()) };
134        NonNull::new(ptr)
135            .map(|ptr| Self { ptr })
136            .ok_or(Error::CopyFailed)
137    }
138
139    pub fn memsize(&self) -> usize {
140        unsafe { tg_geom_sys::tg_line_memsize(self.ptr.as_ptr()) }
141    }
142
143    pub fn index_spread(&self) -> i32 {
144        unsafe { tg_geom_sys::tg_line_index_spread(self.ptr.as_ptr()) }
145    }
146
147    pub fn index_num_levels(&self) -> i32 {
148        unsafe { tg_geom_sys::tg_line_index_num_levels(self.ptr.as_ptr()) }
149    }
150
151    pub fn index_level_num_rects(&self, level: i32) -> i32 {
152        unsafe { tg_geom_sys::tg_line_index_level_num_rects(self.ptr.as_ptr(), level) }
153    }
154
155    pub fn index_level_rect(&self, level: i32, rect_idx: i32) -> Rect {
156        let r =
157            unsafe { tg_geom_sys::tg_line_index_level_rect(self.ptr.as_ptr(), level, rect_idx) };
158        r.into()
159    }
160
161    pub fn iter_points(&self) -> impl Iterator<Item = Point> + '_ {
162        self.points().iter().copied()
163    }
164
165    pub fn iter_segments(&self) -> impl Iterator<Item = Segment> + '_ {
166        (0..self.num_segments()).map(move |i| self.segment_at(i).unwrap())
167    }
168
169    /// Return `false` to stop iteration.
170    pub fn line_search<F>(&self, other: &Line, mut iter: F)
171    where
172        F: FnMut(Segment, usize, Segment, usize) -> bool,
173    {
174        unsafe extern "C" fn trampoline<F>(
175            aseg: tg_geom_sys::tg_segment, aidx: libc::c_int, bseg: tg_geom_sys::tg_segment,
176            bidx: libc::c_int, udata: *mut libc::c_void,
177        ) -> bool
178        where
179            F: FnMut(Segment, usize, Segment, usize) -> bool,
180        {
181            let iter = unsafe { &mut *(udata as *mut F) };
182            iter(aseg.into(), aidx as usize, bseg.into(), bidx as usize)
183        }
184
185        unsafe {
186            tg_geom_sys::tg_line_line_search(
187                self.ptr.as_ptr(),
188                other.as_ptr(),
189                Some(trampoline::<F>),
190                core::ptr::from_mut(&mut iter).cast(),
191            );
192        }
193    }
194}
195
196impl Drop for Line {
197    fn drop(&mut self) {
198        unsafe { tg_geom_sys::tg_line_free(self.ptr.as_ptr()) }
199    }
200}
201
202impl core::fmt::Debug for Line {
203    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204        f.debug_struct("Line")
205            .field("num_points", &self.num_points())
206            .field("num_segments", &self.num_segments())
207            .field("rect", &self.rect())
208            .finish()
209    }
210}
211
212unsafe impl Send for Line {}
213unsafe impl Sync for Line {}
214
215/// A borrowed reference to a line string.
216#[derive(Clone, Copy)]
217pub struct LineRef<'a> {
218    ptr: *const tg_geom_sys::tg_line,
219    _marker: PhantomData<&'a ()>,
220}
221
222unsafe impl Send for LineRef<'_> {}
223unsafe impl Sync for LineRef<'_> {}
224
225impl LineRef<'_> {
226    impl_line_methods!(|s: &Self| s.ptr);
227
228    /// # Safety
229    /// The pointer must be valid for the lifetime `'a`.
230    #[inline]
231    pub unsafe fn from_raw(ptr: *const tg_geom_sys::tg_line) -> Option<Self> {
232        if ptr.is_null() {
233            None
234        } else {
235            Some(Self {
236                ptr,
237                _marker: PhantomData,
238            })
239        }
240    }
241
242    #[inline]
243    pub fn as_ptr(&self) -> *const tg_geom_sys::tg_line {
244        self.ptr
245    }
246
247    pub fn to_owned(&self) -> Result<Line> {
248        let ptr = unsafe { tg_geom_sys::tg_line_copy(self.ptr) };
249        NonNull::new(ptr)
250            .map(|ptr| Line { ptr })
251            .ok_or(Error::CopyFailed)
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    fn p(x: f64, y: f64) -> Point {
260        Point::new(x, y)
261    }
262
263    fn r(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Rect {
264        Rect::from_coords(min_x, min_y, max_x, max_y)
265    }
266
267    fn u1() -> Vec<Point> {
268        vec![p(0.0, 10.0), p(0.0, 0.0), p(10.0, 0.0), p(10.0, 10.0)]
269    }
270
271    fn v1() -> Vec<Point> {
272        vec![p(0.0, 10.0), p(5.0, 0.0), p(10.0, 10.0)]
273    }
274
275    #[test]
276    fn test_line_new() {
277        let line = Line::new(&u1()).unwrap();
278        assert_eq!(line.num_points(), 4);
279        assert_eq!(line.num_segments(), 3);
280    }
281
282    #[test]
283    fn test_line_rect() {
284        let line = Line::new(&u1()).unwrap();
285        assert_eq!(line.rect(), r(0.0, 0.0, 10.0, 10.0));
286    }
287
288    #[test]
289    fn test_line_clockwise() {
290        let cw = Line::new(&[
291            p(0.0, 0.0),
292            p(0.0, 10.0),
293            p(10.0, 10.0),
294            p(10.0, 0.0),
295            p(0.0, 0.0),
296        ])
297        .unwrap();
298        assert!(cw.clockwise());
299
300        let ccw = Line::new(&[
301            p(0.0, 0.0),
302            p(10.0, 0.0),
303            p(10.0, 10.0),
304            p(0.0, 10.0),
305            p(0.0, 0.0),
306        ])
307        .unwrap();
308        assert!(!ccw.clockwise());
309
310        let partial_cw = Line::new(&[p(0.0, 0.0), p(0.0, 10.0), p(10.0, 10.0)]).unwrap();
311        assert!(partial_cw.clockwise());
312
313        let partial_ccw = Line::new(&[p(0.0, 0.0), p(10.0, 0.0), p(10.0, 10.0)]).unwrap();
314        assert!(!partial_ccw.clockwise());
315    }
316
317    #[test]
318    fn test_line_length() {
319        let line = Line::new(&u1()).unwrap();
320        assert!((line.length() - 30.0).abs() < 1e-10);
321    }
322
323    #[test]
324    fn test_line_points() {
325        let line = Line::new(&u1()).unwrap();
326        let points = line.points();
327        assert_eq!(points.len(), 4);
328
329        for (i, &p2) in points.iter().enumerate() {
330            let p1 = line.point_at(i).unwrap();
331            assert_eq!(p1, p2);
332        }
333    }
334
335    #[test]
336    fn test_line_point_at_bounds() {
337        let line = Line::new(&u1()).unwrap();
338        assert!(line.point_at(0).is_some());
339        assert!(line.point_at(3).is_some());
340        assert!(line.point_at(4).is_none());
341        assert!(line.point_at(100).is_none());
342    }
343
344    #[test]
345    fn test_line_segment_at() {
346        let line = Line::new(&u1()).unwrap();
347        assert_eq!(line.num_segments(), 3);
348
349        let seg0 = line.segment_at(0).unwrap();
350        assert_eq!(seg0.a, p(0.0, 10.0));
351        assert_eq!(seg0.b, p(0.0, 0.0));
352
353        assert!(line.segment_at(3).is_none());
354    }
355
356    #[test]
357    fn test_line_copy() {
358        let line = Line::new(&u1()).unwrap();
359        let copy = line.copy().unwrap();
360        assert_eq!(line.num_points(), copy.num_points());
361        assert_eq!(line.length(), copy.length());
362    }
363
364    #[test]
365    fn test_line_clone_ref() {
366        let line = Line::new(&u1()).unwrap();
367        let cloned = line.clone_ref().unwrap();
368        assert_eq!(line.num_points(), cloned.num_points());
369    }
370
371    #[test]
372    fn test_line_memsize() {
373        let line = Line::new(&u1()).unwrap();
374        assert!(line.memsize() > 0);
375    }
376
377    #[test]
378    fn test_line_iter_points() {
379        let line = Line::new(&u1()).unwrap();
380        let points: Vec<Point> = line.iter_points().collect();
381        assert_eq!(points.len(), 4);
382    }
383
384    #[test]
385    fn test_line_iter_segments() {
386        let line = Line::new(&u1()).unwrap();
387        let segments: Vec<Segment> = line.iter_segments().collect();
388        assert_eq!(segments.len(), 3);
389    }
390
391    #[test]
392    fn test_line_line_search() {
393        let line1 = Line::new(&[p(0.0, 0.0), p(10.0, 10.0)]).unwrap();
394        let line2 = Line::new(&[p(0.0, 10.0), p(10.0, 0.0)]).unwrap();
395
396        let mut found = false;
397        line1.line_search(&line2, |_seg1, _idx1, _seg2, _idx2| {
398            found = true;
399            false
400        });
401        assert!(found);
402    }
403
404    #[test]
405    fn test_line_line_search_no_intersection() {
406        let line1 = Line::new(&[p(0.0, 0.0), p(1.0, 1.0)]).unwrap();
407        let line2 = Line::new(&[p(10.0, 10.0), p(11.0, 11.0)]).unwrap();
408
409        let mut count = 0;
410        line1.line_search(&line2, |_seg1, _idx1, _seg2, _idx2| {
411            count += 1;
412            true
413        });
414        assert_eq!(count, 0);
415    }
416
417    #[test]
418    fn test_line_debug() {
419        let line = Line::new(&u1()).unwrap();
420        let debug_str = format!("{line:?}");
421        assert!(debug_str.contains("Line"));
422        assert!(debug_str.contains("num_points"));
423    }
424
425    #[test]
426    fn test_line_send_sync() {
427        fn assert_send<T: Send>() {}
428        fn assert_sync<T: Sync>() {}
429        assert_send::<Line>();
430        assert_sync::<Line>();
431    }
432
433    #[test]
434    fn test_line_index_kinds() {
435        let points = u1();
436        let line_default = Line::new(&points).unwrap();
437        let line_none = Line::new_ix(&points, IndexKind::None).unwrap();
438        let line_natural = Line::new_ix(&points, IndexKind::Natural).unwrap();
439
440        assert_eq!(line_default.num_points(), line_none.num_points());
441        assert_eq!(line_default.num_points(), line_natural.num_points());
442    }
443
444    #[test]
445    fn test_line_raw_pointer() {
446        let line = Line::new(&u1()).unwrap();
447        let num_points = line.num_points();
448        let ptr = line.into_raw();
449        let recovered = unsafe { Line::from_raw(ptr).unwrap() };
450        assert_eq!(recovered.num_points(), num_points);
451    }
452
453    #[test]
454    fn test_line_v_shape() {
455        let line = Line::new(&v1()).unwrap();
456        assert_eq!(line.num_points(), 3);
457        assert_eq!(line.num_segments(), 2);
458        assert_eq!(line.point_at(0).unwrap(), p(0.0, 10.0));
459        assert_eq!(line.point_at(1).unwrap(), p(5.0, 0.0));
460        assert_eq!(line.point_at(2).unwrap(), p(10.0, 10.0));
461    }
462
463    fn point_distance_rect(point: Point, rect: Rect) -> f64 {
464        let x = point.x.clamp(rect.min.x, rect.max.x);
465        let y = point.y.clamp(rect.min.y, rect.max.y);
466        let dx = point.x - x;
467        let dy = point.y - y;
468        (dx * dx + dy * dy).sqrt()
469    }
470
471    fn point_distance_segment(point: Point, seg: Segment) -> f64 {
472        let dx = seg.b.x - seg.a.x;
473        let dy = seg.b.y - seg.a.y;
474        let len_sq = dx * dx + dy * dy;
475
476        if len_sq == 0.0 {
477            let dx = point.x - seg.a.x;
478            let dy = point.y - seg.a.y;
479            return (dx * dx + dy * dy).sqrt();
480        }
481
482        let t = ((point.x - seg.a.x) * dx + (point.y - seg.a.y) * dy) / len_sq;
483        let t = t.clamp(0.0, 1.0);
484
485        let closest_x = seg.a.x + t * dx;
486        let closest_y = seg.a.y + t * dy;
487        let dx = point.x - closest_x;
488        let dy = point.y - closest_y;
489        (dx * dx + dy * dy).sqrt()
490    }
491
492    #[test]
493    fn test_line_nearest_segment_basic() {
494        let line = Line::new_ix(&u1(), IndexKind::Natural).unwrap();
495        let test_point = p(5.0, 5.0);
496        let num_segments = line.num_segments();
497
498        let mut visited = vec![false; num_segments];
499        let mut last_dist = -1.0f64;
500        let mut count = 0usize;
501
502        let completed = line.nearest_segment(
503            |rect| {
504                let dist = point_distance_rect(test_point, rect);
505                (dist, false)
506            },
507            |seg| {
508                let dist = point_distance_segment(test_point, seg);
509                (dist, false)
510            },
511            |seg, dist, idx| {
512                assert!(idx < num_segments);
513                assert!(count == 0 || dist >= last_dist - 1e-10);
514                assert!(!visited[idx]);
515                visited[idx] = true;
516
517                let original_seg = line.segment_at(idx).unwrap();
518                assert_eq!(seg.a, original_seg.a);
519                assert_eq!(seg.b, original_seg.b);
520
521                last_dist = dist;
522                count += 1;
523                true
524            },
525        );
526
527        assert!(completed);
528        assert_eq!(count, num_segments);
529
530        let visited_count = visited.iter().filter(|&&v| v).count();
531        assert_eq!(visited_count, count);
532    }
533
534    #[test]
535    fn test_line_nearest_segment_early_stop() {
536        let line = Line::new_ix(&u1(), IndexKind::Natural).unwrap();
537        let test_point = p(5.0, 5.0);
538        let stop_at = 2;
539        let mut count = 0;
540
541        let success = line.nearest_segment(
542            |rect| (point_distance_rect(test_point, rect), false),
543            |seg| (point_distance_segment(test_point, seg), false),
544            |_seg, _dist, _idx| {
545                count += 1;
546                count < stop_at
547            },
548        );
549
550        assert!(success);
551        assert_eq!(count, stop_at);
552        assert!(count < line.num_segments());
553    }
554
555    #[test]
556    fn test_line_nearest_segment_no_index() {
557        let line = Line::new_ix(&u1(), IndexKind::None).unwrap();
558        let test_point = p(5.0, 5.0);
559        let mut count = 0;
560
561        let completed = line.nearest_segment(
562            |rect| (point_distance_rect(test_point, rect), false),
563            |seg| (point_distance_segment(test_point, seg), false),
564            |_seg, _dist, _idx| {
565                count += 1;
566                true
567            },
568        );
569
570        assert!(completed);
571        assert_eq!(count, line.num_segments());
572    }
573
574    #[test]
575    fn test_line_nearest_segment_closest_first() {
576        let line = Line::new(&u1()).unwrap();
577        let test_point = p(5.0, 0.1);
578
579        let mut first_dist: Option<f64> = None;
580
581        line.nearest_segment(
582            |rect| (point_distance_rect(test_point, rect), false),
583            |seg| (point_distance_segment(test_point, seg), false),
584            |_seg, dist, _idx| {
585                if first_dist.is_none() {
586                    first_dist = Some(dist);
587                }
588                false
589            },
590        );
591
592        let dist = first_dist.expect("Should find at least one segment");
593        assert!(dist < 0.2);
594    }
595}