tg-geom 0.0.0

Rust bindings for the `TG` Geometry library.
Documentation
//! Polygon type with exterior boundary and optional holes.

use core::marker::PhantomData;
use core::ptr::NonNull;

use crate::error::{Error, Result};
use crate::{Rect, Ring, RingRef};

macro_rules! impl_poly_methods {
    ($get_ptr:expr, $life:lifetime) => {
        #[inline]
        pub fn rect(&self) -> Rect {
            let ptr = $get_ptr(self);
            let r = unsafe { tg_geom_sys::tg_poly_rect(ptr) };
            r.into()
        }

        #[inline]
        pub fn num_holes(&self) -> usize {
            let ptr = $get_ptr(self);
            unsafe { tg_geom_sys::tg_poly_num_holes(ptr) as usize }
        }

        #[inline]
        pub fn clockwise(&self) -> bool {
            let ptr = $get_ptr(self);
            unsafe { tg_geom_sys::tg_poly_clockwise(ptr) }
        }

        #[inline]
        pub fn exterior(&self) -> RingRef<$life> {
            let ptr = $get_ptr(self);
            let ptr = unsafe { tg_geom_sys::tg_poly_exterior(ptr) };
            unsafe { RingRef::from_raw(ptr).unwrap() }
        }

        #[inline]
        pub fn hole_at(&self, index: usize) -> Option<RingRef<$life>> {
            if index >= self.num_holes() {
                None
            } else {
                let ptr = $get_ptr(self);
                let ptr = unsafe { tg_geom_sys::tg_poly_hole_at(ptr, index as libc::c_int) };
                unsafe { RingRef::from_raw(ptr) }
            }
        }
    };
}

/// An owned polygon.
pub struct Poly {
    ptr: NonNull<tg_geom_sys::tg_poly>,
}

impl Poly {
    impl_poly_methods!(|s: &Self| s.ptr.as_ptr(), '_);

    pub fn new(exterior: &Ring, holes: &[&Ring]) -> Result<Self> {
        let hole_ptrs: Vec<*const tg_geom_sys::tg_ring> =
            holes.iter().map(|r| r.as_ptr()).collect();

        let ptr = unsafe {
            tg_geom_sys::tg_poly_new(
                exterior.as_ptr(),
                hole_ptrs.as_ptr(),
                holes.len() as libc::c_int,
            )
        };
        NonNull::new(ptr)
            .map(|ptr| Self { ptr })
            .ok_or(Error::OutOfMemory)
    }

    pub fn new_simple(exterior: &Ring) -> Result<Self> {
        let ptr = unsafe { tg_geom_sys::tg_poly_new(exterior.as_ptr(), core::ptr::null(), 0) };
        NonNull::new(ptr)
            .map(|ptr| Self { ptr })
            .ok_or(Error::OutOfMemory)
    }

    /// # Safety
    /// The pointer must be valid and not owned elsewhere.
    pub unsafe fn from_raw(ptr: *mut tg_geom_sys::tg_poly) -> Option<Self> {
        NonNull::new(ptr).map(|ptr| Self { ptr })
    }

    #[inline]
    pub fn as_ptr(&self) -> *const tg_geom_sys::tg_poly {
        self.ptr.as_ptr()
    }

    pub fn into_raw(self) -> *mut tg_geom_sys::tg_poly {
        let ptr = self.ptr.as_ptr();
        core::mem::forget(self);
        ptr
    }

    pub fn copy(&self) -> Result<Self> {
        let ptr = unsafe { tg_geom_sys::tg_poly_copy(self.ptr.as_ptr()) };
        NonNull::new(ptr)
            .map(|ptr| Self { ptr })
            .ok_or(Error::CopyFailed)
    }

    pub fn clone_ref(&self) -> Result<Self> {
        let ptr = unsafe { tg_geom_sys::tg_poly_clone(self.ptr.as_ptr()) };
        NonNull::new(ptr)
            .map(|ptr| Self { ptr })
            .ok_or(Error::CopyFailed)
    }

    pub fn memsize(&self) -> usize {
        unsafe { tg_geom_sys::tg_poly_memsize(self.ptr.as_ptr()) }
    }

    pub fn iter_holes(&self) -> impl Iterator<Item = RingRef<'_>> {
        (0..self.num_holes()).map(move |i| self.hole_at(i).unwrap())
    }
}

impl Drop for Poly {
    fn drop(&mut self) {
        unsafe { tg_geom_sys::tg_poly_free(self.ptr.as_ptr()) }
    }
}

impl core::fmt::Debug for Poly {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Poly")
            .field("num_holes", &self.num_holes())
            .field("rect", &self.rect())
            .field("clockwise", &self.clockwise())
            .finish()
    }
}

unsafe impl Send for Poly {}
unsafe impl Sync for Poly {}

/// A borrowed reference to a polygon.
#[derive(Clone, Copy)]
pub struct PolyRef<'a> {
    ptr: *const tg_geom_sys::tg_poly,
    _marker: PhantomData<&'a ()>,
}

unsafe impl Send for PolyRef<'_> {}
unsafe impl Sync for PolyRef<'_> {}

impl<'a> PolyRef<'a> {
    impl_poly_methods!(|s: &Self| s.ptr, 'a);

    /// # Safety
    /// The pointer must be valid for the lifetime `'a`.
    #[inline]
    pub unsafe fn from_raw(ptr: *const tg_geom_sys::tg_poly) -> Option<Self> {
        if ptr.is_null() {
            None
        } else {
            Some(Self {
                ptr,
                _marker: PhantomData,
            })
        }
    }

    #[inline]
    pub fn as_ptr(&self) -> *const tg_geom_sys::tg_poly {
        self.ptr
    }

    pub fn to_owned(&self) -> Result<Poly> {
        let ptr = unsafe { tg_geom_sys::tg_poly_copy(self.ptr) };
        NonNull::new(ptr)
            .map(|ptr| Poly { ptr })
            .ok_or(Error::CopyFailed)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Point;

    fn p(x: f64, y: f64) -> Point {
        Point::new(x, y)
    }

    fn r(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Rect {
        Rect::from_coords(min_x, min_y, max_x, max_y)
    }

    fn octagon() -> Vec<Point> {
        vec![
            p(3.0, 0.0),
            p(7.0, 0.0),
            p(10.0, 3.0),
            p(10.0, 7.0),
            p(7.0, 10.0),
            p(3.0, 10.0),
            p(0.0, 7.0),
            p(0.0, 3.0),
            p(3.0, 0.0),
        ]
    }

    fn small_hole() -> Vec<Point> {
        vec![
            p(4.0, 4.0),
            p(6.0, 4.0),
            p(6.0, 6.0),
            p(4.0, 6.0),
            p(4.0, 4.0),
        ]
    }

    fn rect_ring() -> Vec<Point> {
        vec![
            p(0.0, 0.0),
            p(10.0, 0.0),
            p(10.0, 10.0),
            p(0.0, 10.0),
            p(0.0, 0.0),
        ]
    }

    #[test]
    fn test_poly_rect() {
        let exterior = Ring::new(&octagon()).unwrap();
        let poly = Poly::new_simple(&exterior).unwrap();
        assert_eq!(poly.rect(), r(0.0, 0.0, 10.0, 10.0));
    }

    #[test]
    fn test_poly_rect_with_hole() {
        let exterior = Ring::new(&octagon()).unwrap();
        let hole = Ring::new(&small_hole()).unwrap();
        let poly = Poly::new(&exterior, &[&hole]).unwrap();
        assert_eq!(poly.rect(), r(0.0, 0.0, 10.0, 10.0));
    }

    #[test]
    fn test_poly_exterior_holes() {
        let exterior = Ring::new(&octagon()).unwrap();
        let poly = Poly::new_simple(&exterior).unwrap();
        assert_eq!(poly.num_holes(), 0);
        assert_eq!(poly.exterior().num_points(), 9);
    }

    #[test]
    fn test_poly_with_hole() {
        let exterior = Ring::new(&octagon()).unwrap();
        let hole = Ring::new(&small_hole()).unwrap();
        let poly = Poly::new(&exterior, &[&hole]).unwrap();

        assert_eq!(poly.num_holes(), 1);
        let hole_ref = poly.hole_at(0).unwrap();
        assert_eq!(hole_ref.num_points(), 5);
    }

    #[test]
    fn test_poly_hole_at_bounds() {
        let exterior = Ring::new(&octagon()).unwrap();
        let hole = Ring::new(&small_hole()).unwrap();
        let poly = Poly::new(&exterior, &[&hole]).unwrap();

        assert!(poly.hole_at(0).is_some());
        assert!(poly.hole_at(1).is_none());
        assert!(poly.hole_at(100).is_none());
    }

    #[test]
    fn test_poly_no_holes() {
        let exterior = Ring::new(&rect_ring()).unwrap();
        let poly = Poly::new_simple(&exterior).unwrap();
        assert_eq!(poly.num_holes(), 0);
        assert!(poly.hole_at(0).is_none());
    }

    #[test]
    fn test_poly_clockwise() {
        let ccw_exterior = Ring::new(&[
            p(0.0, 0.0),
            p(10.0, 0.0),
            p(10.0, 10.0),
            p(0.0, 10.0),
            p(0.0, 0.0),
        ])
        .unwrap();
        let poly_ccw = Poly::new_simple(&ccw_exterior).unwrap();
        assert!(!poly_ccw.clockwise());

        let cw_exterior = Ring::new(&[
            p(0.0, 0.0),
            p(0.0, 10.0),
            p(10.0, 10.0),
            p(10.0, 0.0),
            p(0.0, 0.0),
        ])
        .unwrap();
        let poly_cw = Poly::new_simple(&cw_exterior).unwrap();
        assert!(poly_cw.clockwise());
    }

    #[test]
    fn test_poly_copy() {
        let exterior = Ring::new(&octagon()).unwrap();
        let hole = Ring::new(&small_hole()).unwrap();
        let poly = Poly::new(&exterior, &[&hole]).unwrap();

        let copy = poly.copy().unwrap();
        assert_eq!(poly.num_holes(), copy.num_holes());
        assert_eq!(poly.rect(), copy.rect());
    }

    #[test]
    fn test_poly_clone_ref() {
        let exterior = Ring::new(&octagon()).unwrap();
        let poly = Poly::new_simple(&exterior).unwrap();
        let cloned = poly.clone_ref().unwrap();
        assert_eq!(poly.num_holes(), cloned.num_holes());
    }

    #[test]
    fn test_poly_memsize() {
        let exterior = Ring::new(&octagon()).unwrap();
        let poly = Poly::new_simple(&exterior).unwrap();
        assert!(poly.memsize() > 0);
    }

    #[test]
    fn test_poly_iter_holes() {
        let exterior = Ring::new(&rect_ring()).unwrap();
        let hole1 = Ring::new(&[
            p(2.0, 2.0),
            p(3.0, 2.0),
            p(3.0, 3.0),
            p(2.0, 3.0),
            p(2.0, 2.0),
        ])
        .unwrap();
        let hole2 = Ring::new(&[
            p(6.0, 6.0),
            p(7.0, 6.0),
            p(7.0, 7.0),
            p(6.0, 7.0),
            p(6.0, 6.0),
        ])
        .unwrap();
        let poly = Poly::new(&exterior, &[&hole1, &hole2]).unwrap();

        let holes: Vec<RingRef<'_>> = poly.iter_holes().collect();
        assert_eq!(holes.len(), 2);
    }

    #[test]
    fn test_poly_debug() {
        let exterior = Ring::new(&octagon()).unwrap();
        let poly = Poly::new_simple(&exterior).unwrap();
        let debug_str = format!("{poly:?}");
        assert!(debug_str.contains("Poly"));
        assert!(debug_str.contains("num_holes"));
    }

    #[test]
    fn test_poly_send_sync() {
        fn assert_send<T: Send>() {}
        fn assert_sync<T: Sync>() {}
        assert_send::<Poly>();
        assert_sync::<Poly>();
    }

    #[test]
    fn test_poly_raw_pointer() {
        let exterior = Ring::new(&octagon()).unwrap();
        let poly = Poly::new_simple(&exterior).unwrap();
        let num_holes = poly.num_holes();
        let ptr = poly.into_raw();
        let recovered = unsafe { Poly::from_raw(ptr).unwrap() };
        assert_eq!(recovered.num_holes(), num_holes);
    }

    #[test]
    fn test_polyref_to_owned() {
        let exterior = Ring::new(&octagon()).unwrap();
        let poly = Poly::new_simple(&exterior).unwrap();

        let poly_ref = unsafe { PolyRef::from_raw(poly.as_ptr()).unwrap() };
        let owned = poly_ref.to_owned().unwrap();
        assert_eq!(owned.num_holes(), 0);
    }
}