geos 5.0.0

Rust bindings for GEOS C API
Documentation
use enums::*;
use error::{Error, GResult, PredicateType};
use geos_sys::*;
use geometry::Geometry;
use std::ffi::CStr;
use std::sync::Arc;
use std::str;
use crate::{ContextHandle, AsRaw, ContextHandling};
use context_handle::PtrWrap;

// We need to cleanup only the char* from geos, the const char* are not to be freed.
// this has to be checked method by method in geos
// so we provide 2 method to wrap a char* to a string, one that manage (and thus free) the underlying char*
// and one that does not free it
pub(crate) unsafe fn unmanaged_string(raw_ptr: *const i8, caller: &str) -> GResult<String> {
    if raw_ptr.is_null() {
        return Err(Error::NoConstructionFromNullPtr(format!("{}::unmanaged_string", caller)));
    }
    let c_str = CStr::from_ptr(raw_ptr);
    match str::from_utf8(c_str.to_bytes()) {
        Ok(s) => Ok(s.to_string()),
        Err(e) => {
            Err(Error::GenericError(format!("{}::unmanaged_string failed: {}", caller, e)))
        }
    }
}

pub(crate) unsafe fn managed_string(
    raw_ptr: *mut i8,
    context: &ContextHandle,
    caller: &str,
) -> GResult<String> {
    if raw_ptr.is_null() {
        return Err(Error::NoConstructionFromNullPtr(format!("{}::managed_string", caller)));
    }
    let s = unmanaged_string(raw_ptr, caller);
    GEOSFree_r(context.as_raw(), raw_ptr as *mut _);
    s
}

#[allow(dead_code)]
pub fn clip_by_rect<'a>(
    g: &Geometry<'a>,
    xmin: f64,
    ymin: f64,
    xmax: f64,
    ymax: f64,
) -> GResult<Geometry<'a>> {
    unsafe {
        let context = g.clone_context();
        let ptr = GEOSClipByRect_r(
            context.as_raw(),
            g.as_raw(),
            xmin,
            ymin,
            xmax,
            ymax,
        );
        Geometry::new_from_raw(ptr, context, "clip_by_rect")
    }
}

pub fn version() -> GResult<String> {
    unsafe { unmanaged_string(GEOSversion(), "version") }
}

pub(crate) fn check_geos_predicate(val: i32, p: PredicateType) -> GResult<bool> {
    match val {
        1 => Ok(true),
        0 => Ok(false),
        _ => Err(Error::GeosFunctionError(p, val)),
    }
}

pub(crate) fn check_ret(val: i32, p: PredicateType) -> GResult<()> {
    match val {
        1 => Ok(()),
        _ => Err(Error::GeosFunctionError(p, val)),
    }
}

pub(crate) fn check_same_geometry_type(geoms: &[Geometry], geom_type: GeometryTypes) -> bool {
    geoms.iter().all(|g| g.geometry_type() == geom_type)
}

pub(crate) fn create_multi_geom<'a>(
    mut geoms: Vec<Geometry<'a>>,
    output_type: GeometryTypes,
) -> GResult<Geometry<'a>> {
    let nb_geoms = geoms.len();
    let context = if geoms.is_empty() {
        match ContextHandle::init() {
            Ok(ch) => Arc::new(ch),
            _ => return Err(Error::GenericError("GEOS_init_r failed".to_owned())),
        }
    } else {
        geoms[0].clone_context()
    };
    let res = {
        let mut geoms: Vec<*mut GEOSGeometry> = geoms.iter_mut().map(|g| g.as_raw()).collect();
        unsafe {
            let ptr = GEOSGeom_createCollection_r(
                context.as_raw(),
                output_type.into(),
                geoms.as_mut_ptr() as *mut *mut GEOSGeometry,
                nb_geoms as _,
            );
            Geometry::new_from_raw(ptr, context, "create_multi_geom")
        }
    };

    // we'll transfert the ownership of the ptr to the new Geometry,
    // so the old one needs to forget their c ptr to avoid double cleanup
    for g in geoms.iter_mut() {
        g.ptr = PtrWrap(::std::ptr::null_mut());
    }

    res
}

pub fn orientation_index(
    ax: f64,
    ay: f64,
    bx: f64,
    by: f64,
    px: f64,
    py: f64,
) -> GResult<Orientation> {
    match ContextHandle::init() {
        Ok(context) => {
            unsafe {
                match Orientation::try_from(
                    GEOSOrientationIndex_r(context.as_raw(), ax, ay, bx, by, px, py)
                ) {
                    Ok(o) => Ok(o),
                    Err(e) => Err(Error::GenericError(e.to_owned())),
                }
            }
        }
        Err(e) => Err(e),
    }
}

/// Returns [`None`] if the segments don't intersect, otherwise returns `Some(x_pos, y_pos)`.
///
/// Available using the `v3_7_0` feature.
#[cfg(any(feature = "v3_7_0", feature = "dox"))]
pub fn segment_intersection(
    ax0: f64,
    ay0: f64,
    ax1: f64,
    ay1: f64,
    bx0: f64,
    by0: f64,
    bx1: f64,
    by1: f64,
) -> GResult<Option<(f64, f64)>> {
    match ContextHandle::init() {
        Ok(context) => {
            unsafe {
                let mut cx = 0.;
                let mut cy = 0.;

                let ret = GEOSSegmentIntersection_r(
                    context.as_raw(), ax0, ay0, ax1, ay1, bx0, by0, bx1, by1, &mut cx, &mut cy);
                if ret == -1 {
                    Ok(None)
                } else if ret == 0 {
                    Ok(Some((cx, cy)))
                } else {
                    Err(Error::GenericError("GEOSSegmentIntersection_r failed".to_owned()))
                }
            }
        }
        Err(e) => Err(e),
    }
}

#[cfg(test)]
mod test {
    use super::check_geos_predicate;
    use error::PredicateType;

    #[test]
    fn check_geos_predicate_ok_test() {
        assert_eq!(
            check_geos_predicate(0, PredicateType::Intersects).unwrap(),
            false
        );
    }

    #[test]
    fn check_geos_predicate_ko_test() {
        assert_eq!(
            check_geos_predicate(1, PredicateType::Intersects).unwrap(),
            true
        );
    }

    #[test]
    fn check_geos_predicate_err_test() {
        let r = check_geos_predicate(42, PredicateType::Intersects);
        let e = r.err().unwrap();

        assert_eq!(
            format!("{}", e),
            "error while calling libgeos method Intersects (error number = 42)".to_string()
        );
    }
}