geos 8.0.0

Rust bindings for GEOS C API
Documentation
use crate::{
    AsRaw, AsRawMut, ContextHandle, ContextHandling, ContextInteractions, CoordDimensions,
    Geometry, Ordinate,
};
use context_handle::PtrWrap;
use error::{Error, GResult};
use geos_sys::*;
use std::convert::TryFrom;
use std::sync::Arc;

/// `CoordSeq` represents a list of coordinates inside a [`Geometry`].
///
/// # Example
///
/// ```
/// use geos::{CoordDimensions, CoordSeq};
///
/// let mut coords = CoordSeq::new(1, CoordDimensions::OneD)
///                           .expect("failed to create CoordSeq");
/// coords.set_x(0, 10.);
/// assert_eq!(coords.get_x(0), Ok(10.));
/// ```
pub struct CoordSeq<'a> {
    pub(crate) ptr: PtrWrap<*mut GEOSCoordSequence>,
    pub(crate) context: Arc<ContextHandle<'a>>,
    nb_dimensions: usize,
    nb_lines: usize,
}

impl<'a> CoordSeq<'a> {
    /// Creates a new `CoordSeq`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let mut coord_seq = CoordSeq::new(2, CoordDimensions::ThreeD)
    ///                              .expect("failed to create CoordSeq");
    ///
    /// // Then you fill the positions of your `coord_seq`:
    /// let positions: &[(f64, f64, f64)] = &[(0., 0., 0.), (1., 2., 1.)];
    /// for (pos, (x, y, z)) in positions.into_iter().enumerate() {
    ///     coord_seq.set_x(pos, *x).expect("failed to set x...");
    ///     coord_seq.set_y(pos, *y).expect("failed to set y...");
    ///     coord_seq.set_z(pos, *z).expect("failed to set z...");
    /// }
    /// assert_eq!(coord_seq.get_z(1), Ok(1.));
    ///
    /// // An example with 2 dimensions (and 3 lines) as well:
    /// let mut coord_seq2 = CoordSeq::new(3, CoordDimensions::TwoD)
    ///                               .expect("failed to create CoordSeq");
    /// let positions2: &[(f64, f64)] = &[(0., 0.), (1., 2.), (14., 5.)];
    /// for (pos, (x, y)) in positions2.into_iter().enumerate() {
    ///     coord_seq2.set_x(pos, *x).expect("failed to set x...");
    ///     coord_seq2.set_y(pos, *y).expect("failed to set y...");
    /// }
    /// assert_eq!(coord_seq2.get_x(1), Ok(1.));
    /// ```
    pub fn new(size: u32, dims: CoordDimensions) -> GResult<CoordSeq<'a>> {
        match ContextHandle::init_e(Some("CoordSeq::new")) {
            Ok(context_handle) => unsafe {
                let ptr = GEOSCoordSeq_create_r(context_handle.as_raw(), size, dims.into());
                CoordSeq::new_from_raw(ptr, Arc::new(context_handle), size, dims.into(), "new")
            },
            Err(e) => Err(e),
        }
    }

    /// Creates a new `CoordSeq`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::CoordSeq;
    ///
    /// let coords = CoordSeq::new_from_vec(&[&[0., 1.], &[2., 3.]])
    ///                       .expect("failed to create CoordSeq");
    /// assert_eq!(coords.get_y(1), Ok(3.));
    ///
    /// // Doing it from a Vec<Vec<f64>>.
    /// let positions = vec![vec![0., 1.], vec![2., 3.]];
    /// let s_positions = positions.iter().map(|x| x.as_slice()).collect::<Vec<_>>();
    /// let coords = CoordSeq::new_from_vec(&s_positions)
    ///                       .expect("failed to create CoordSeq");
    /// assert_eq!(coords.get_y(1), Ok(3.));
    ///
    /// // All vectors don't have the same length, this is an error!
    /// assert!(CoordSeq::new_from_vec(&[vec![0., 1.], vec![3.]]).is_err());
    ///
    /// // An empty vector is an error as well since we can't figure out its dimensions!
    /// let x: &[f64] = &[];
    /// assert!(CoordSeq::new_from_vec(&[x]).is_err());
    /// ```
    pub fn new_from_vec<T: AsRef<[f64]>>(data: &[T]) -> GResult<CoordSeq<'a>> {
        let size = data.len();

        if size > 0 {
            let dims = data[0].as_ref().len();
            if let Err(e) = CoordDimensions::try_from(dims as u32) {
                return Err(Error::GenericError(e.to_owned()));
            }
            if !data.iter().skip(1).all(|x| x.as_ref().len() == dims) {
                return Err(Error::GenericError(
                    "All vec entries must have the same size!".into(),
                ));
            }
            match ContextHandle::init_e(Some("CoordSeq::new_from_vec")) {
                Ok(context_handle) => unsafe {
                    let ptr = GEOSCoordSeq_create_r(context_handle.as_raw(), size as _, dims as _);
                    CoordSeq::new_from_raw(
                        ptr,
                        Arc::new(context_handle),
                        size as _,
                        dims as _,
                        "new_from_vec",
                    )
                },
                Err(e) => return Err(e),
            }
            .and_then(|mut coord| {
                let raw_context = coord.get_raw_context();
                let raw_coord = coord.as_raw_mut();

                let funcs = [
                    GEOSCoordSeq_setX_r,
                    GEOSCoordSeq_setY_r,
                    GEOSCoordSeq_setZ_r,
                ];

                for (line, line_data) in data.iter().enumerate() {
                    for (pos, elem) in line_data.as_ref().iter().enumerate() {
                        unsafe {
                            if funcs[pos](raw_context, raw_coord, line as _, *elem) == 0 {
                                let err = format!(
                                    "Failed to set value at position {} on \
                                                   line {}",
                                    pos, line
                                );
                                return Err(Error::GenericError(err));
                            }
                        }
                    }
                }
                Ok(coord)
            })
        } else {
            Err(Error::GenericError(
                "Can't determine dimension for the CoordSeq".to_owned(),
            ))
        }
    }

    pub(crate) unsafe fn new_from_raw(
        ptr: *mut GEOSCoordSequence,
        context: Arc<ContextHandle<'a>>,
        size: u32,
        dims: u32,
        caller: &str,
    ) -> GResult<CoordSeq<'a>> {
        if ptr.is_null() {
            let extra = if let Some(x) = context.get_last_error() {
                format!("\nLast error: {}", x)
            } else {
                String::new()
            };
            return Err(Error::NoConstructionFromNullPtr(format!(
                "CoordSeq::{}{}",
                caller, extra
            )));
        }
        Ok(CoordSeq {
            ptr: PtrWrap(ptr),
            context,
            nb_dimensions: dims as _,
            nb_lines: size as _,
        })
    }

    /// Sets the X position value at the given `line`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let mut coords = CoordSeq::new(1, CoordDimensions::OneD)
    ///                           .expect("failed to create CoordSeq");
    /// coords.set_x(0, 10.);
    /// assert_eq!(coords.get_x(0), Ok(10.));
    /// ```
    pub fn set_x(&mut self, line: usize, val: f64) -> GResult<()> {
        assert!(line < self.nb_lines);

        let ret_val = unsafe {
            GEOSCoordSeq_setX_r(self.get_raw_context(), self.as_raw_mut(), line as _, val)
        };
        if ret_val == 0 {
            Err(Error::GeosError("impossible to set x for coord".into()))
        } else {
            Ok(())
        }
    }

    /// Sets the Y position value at the given `line`.
    ///
    /// Note: your `CoordSeq` object must have at least two dimensions!
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let mut coords = CoordSeq::new(1, CoordDimensions::TwoD)
    ///                           .expect("failed to create CoordSeq");
    /// coords.set_y(0, 10.);
    /// assert_eq!(coords.get_y(0), Ok(10.));
    /// ```
    pub fn set_y(&mut self, line: usize, val: f64) -> GResult<()> {
        assert!(line < self.nb_lines);
        assert!(self.nb_dimensions >= 2);

        let ret_val = unsafe {
            GEOSCoordSeq_setY_r(self.get_raw_context(), self.as_raw_mut(), line as _, val)
        };
        if ret_val == 0 {
            Err(Error::GeosError("impossible to set y for coord".into()))
        } else {
            Ok(())
        }
    }

    /// Sets the Z position value at the given `line`.
    ///
    /// Note: your `CoordSeq` object must have three dimensions!
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let mut coords = CoordSeq::new(1, CoordDimensions::ThreeD)
    ///                           .expect("failed to create CoordSeq");
    /// coords.set_z(0, 10.);
    /// assert_eq!(coords.get_z(0), Ok(10.));
    /// ```
    pub fn set_z(&mut self, line: usize, val: f64) -> GResult<()> {
        assert!(line < self.nb_lines);
        assert!(self.nb_dimensions >= 3);

        let ret_val = unsafe {
            GEOSCoordSeq_setZ_r(self.get_raw_context(), self.as_raw_mut(), line as _, val)
        };
        if ret_val == 0 {
            Err(Error::GeosError("impossible to set z for coord".into()))
        } else {
            Ok(())
        }
    }

    /// Sets the value at the given `ordinate` (aka position).
    ///
    /// Note: your `CoordSeq` object must have enough dimensions to set at the given `ordinate`!
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq, Ordinate};
    ///
    /// let mut coords = CoordSeq::new(1, CoordDimensions::ThreeD)
    ///                           .expect("failed to create CoordSeq");
    /// coords.set_ordinate(0, Ordinate::Z, 10.);
    /// assert_eq!(coords.get_z(0), Ok(10.));
    /// assert_eq!(coords.get_ordinate(0, Ordinate::Z), Ok(10.));
    /// ```
    pub fn set_ordinate(&mut self, line: usize, ordinate: Ordinate, val: f64) -> GResult<()> {
        let ordinate: u32 = ordinate.into();
        assert!(line < self.nb_lines);
        assert!(self.nb_dimensions > ordinate as _);

        let ret_val = unsafe {
            GEOSCoordSeq_setOrdinate_r(
                self.get_raw_context(),
                self.as_raw_mut(),
                line as _,
                ordinate,
                val,
            )
        };
        if ret_val == 0 {
            Err(Error::GeosError(format!(
                "impossible to set value for ordinate {}",
                ordinate
            )))
        } else {
            Ok(())
        }
    }

    /// Gets the X position value at the given `line`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let mut coords = CoordSeq::new(1, CoordDimensions::OneD)
    ///                           .expect("failed to create CoordSeq");
    /// coords.set_x(0, 10.);
    /// assert_eq!(coords.get_x(0), Ok(10.));
    /// ```
    pub fn get_x(&self, line: usize) -> GResult<f64> {
        assert!(line < self.nb_lines);

        let mut n = 0.;
        let ret_val = unsafe {
            GEOSCoordSeq_getX_r(self.get_raw_context(), self.as_raw(), line as _, &mut n)
        };
        if ret_val == 0 {
            Err(Error::GeosError(
                "failed to get coordinates from CoordSeq".into(),
            ))
        } else {
            Ok(n as f64)
        }
    }

    /// Gets the Y position value at the given `line`.
    ///
    /// Note: your `CoordSeq` object must have at least two dimensions!
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let mut coords = CoordSeq::new(1, CoordDimensions::TwoD)
    ///                           .expect("failed to create CoordSeq");
    /// coords.set_y(0, 10.);
    /// assert_eq!(coords.get_y(0), Ok(10.));
    /// ```
    pub fn get_y(&self, line: usize) -> GResult<f64> {
        assert!(line < self.nb_lines);
        assert!(self.nb_dimensions >= 2);

        let mut n = 0.;
        let ret_val = unsafe {
            GEOSCoordSeq_getY_r(self.get_raw_context(), self.as_raw(), line as _, &mut n)
        };
        if ret_val == 0 {
            Err(Error::GeosError(
                "failed to get coordinates from CoordSeq".into(),
            ))
        } else {
            Ok(n as f64)
        }
    }

    /// Gets the Z position value at the given `line`.
    ///
    /// Note: your `CoordSeq` object must have three dimensions!
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let mut coords = CoordSeq::new(1, CoordDimensions::ThreeD)
    ///                           .expect("failed to create CoordSeq");
    /// coords.set_z(0, 10.);
    /// assert_eq!(coords.get_z(0), Ok(10.));
    /// ```
    pub fn get_z(&self, line: usize) -> GResult<f64> {
        assert!(line < self.nb_lines);
        assert!(self.nb_dimensions >= 3);

        let mut n = 0.;
        let ret_val = unsafe {
            GEOSCoordSeq_getZ_r(self.get_raw_context(), self.as_raw(), line as _, &mut n)
        };
        if ret_val == 0 {
            Err(Error::GeosError(
                "failed to get coordinates from CoordSeq".into(),
            ))
        } else {
            Ok(n as f64)
        }
    }

    /// Gets the value at the given `ordinate` (aka position).
    ///
    /// Note: your `CoordSeq` object must have enough dimensions to access the given `ordinate`!
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq, Ordinate};
    ///
    /// let mut coords = CoordSeq::new(1, CoordDimensions::ThreeD)
    ///                           .expect("failed to create CoordSeq");
    /// coords.set_ordinate(0, Ordinate::Z, 10.);
    /// assert_eq!(coords.get_z(0), Ok(10.));
    /// assert_eq!(coords.get_ordinate(0, Ordinate::Z), Ok(10.));
    /// ```
    pub fn get_ordinate(&self, line: usize, ordinate: Ordinate) -> GResult<f64> {
        let ordinate: u32 = ordinate.into();
        let mut val = 0f64;
        assert!(line < self.nb_lines);
        assert!(self.nb_dimensions > ordinate as _);

        if unsafe {
            GEOSCoordSeq_getOrdinate_r(
                self.get_raw_context(),
                self.as_raw(),
                line as _,
                ordinate,
                &mut val,
            )
        } != 1
        {
            Err(Error::GeosError("getting size from CoordSeq".into()))
        } else {
            Ok(val)
        }
    }

    /// Returns the number of lines of the `CoordSeq` object.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let coords = CoordSeq::new(2, CoordDimensions::ThreeD)
    ///                       .expect("failed to create CoordSeq");
    /// assert_eq!(coords.size(), Ok(2));
    ///
    /// let coords = CoordSeq::new_from_vec(&[&[1f64], &[2.], &[3.], &[4.]])
    ///                       .expect("failed to create CoordSeq");
    /// assert_eq!(coords.size(), Ok(4));
    /// ```
    pub fn size(&self) -> GResult<usize> {
        let mut n = 0;
        let ret_val =
            unsafe { GEOSCoordSeq_getSize_r(self.get_raw_context(), self.as_raw(), &mut n) };
        if ret_val == 0 {
            Err(Error::GeosError("getting size from CoordSeq".into()))
        } else {
            Ok(n as usize)
        }
    }

    /// Returns the number of lines of the `CoordSeq` object.
    ///
    /// Note: This is an alias to the [`size`](#method.size) method.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let coords = CoordSeq::new(2, CoordDimensions::ThreeD)
    ///                       .expect("failed to create CoordSeq");
    /// assert_eq!(coords.number_of_lines(), Ok(2));
    ///
    /// let coords = CoordSeq::new_from_vec(&[&[1f64], &[2.], &[3.], &[4.]])
    ///                       .expect("failed to create CoordSeq");
    /// assert_eq!(coords.number_of_lines(), Ok(4));
    /// ```
    pub fn number_of_lines(&self) -> GResult<usize> {
        self.size()
    }

    /// Returns the number of dimensions of the `CoordSeq` object.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq};
    ///
    /// let coords = CoordSeq::new(2, CoordDimensions::OneD)
    ///                       .expect("failed to create CoordSeq");
    /// assert_eq!(coords.dimensions(), Ok(CoordDimensions::OneD));
    ///
    /// let coords = CoordSeq::new_from_vec(&[&[1., 2.], &[3. ,4.]])
    ///                       .expect("failed to create CoordSeq");
    /// assert_eq!(coords.dimensions(), Ok(CoordDimensions::TwoD));
    /// ```
    pub fn dimensions(&self) -> GResult<CoordDimensions> {
        let mut n = 0;
        let ret_val =
            unsafe { GEOSCoordSeq_getDimensions_r(self.get_raw_context(), self.as_raw(), &mut n) };
        if ret_val == 0 {
            Err(Error::GeosError("getting dimensions from CoordSeq".into()))
        } else {
            Ok(CoordDimensions::try_from(n).expect("Failed to convert to CoordDimensions"))
        }
    }

    /// Returns `true` if the geometry has a counter-clockwise orientation.
    ///
    /// Available using the `v3_7_0` feature.
    #[cfg(any(feature = "v3_7_0", feature = "dox"))]
    pub fn is_ccw(&self) -> GResult<bool> {
        unsafe {
            let mut is_ccw = 0;
            if GEOSCoordSeq_isCCW_r(self.get_raw_context(), self.as_raw(), &mut is_ccw) != 1 {
                Err(Error::GenericError(
                    "GEOSCoordSeq_isCCW_r failed".to_owned(),
                ))
            } else {
                Ok(is_ccw == 1)
            }
        }
    }

    /// Creates a point geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq, Geom, Geometry};
    ///
    /// let coords = CoordSeq::new_from_vec(&[&[1., 2.]])
    ///                       .expect("failed to create CoordSeq");
    ///
    /// let geom = Geometry::create_point(coords).expect("Failed to create point");
    ///
    /// assert_eq!(geom.to_wkt().unwrap(), "POINT (1.0000000000000000 2.0000000000000000)");
    /// ```
    pub fn create_point(self) -> GResult<Geometry<'a>> {
        Geometry::create_point(self)
    }

    /// Creates a line string geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq, Geom, Geometry};
    ///
    /// let coords = CoordSeq::new_from_vec(&[&[1., 2.], &[3., 4.]])
    ///                       .expect("failed to create CoordSeq");
    ///
    /// let geom = Geometry::create_line_string(coords).expect("Failed to create line string");
    ///
    /// assert_eq!(geom.to_wkt().unwrap(),
    ///            "LINESTRING (1.0000000000000000 2.0000000000000000, \
    ///                         3.0000000000000000 4.0000000000000000)");
    /// ```
    pub fn create_line_string(self) -> GResult<Geometry<'a>> {
        Geometry::create_line_string(self)
    }

    /// Creates a linear ring geometry.
    pub fn create_linear_ring(self) -> GResult<Geometry<'a>> {
        Geometry::create_linear_ring(self)
    }
}

unsafe impl<'a> Send for CoordSeq<'a> {}
unsafe impl<'a> Sync for CoordSeq<'a> {}

impl<'a> Drop for CoordSeq<'a> {
    fn drop(&mut self) {
        if self.ptr.is_null() {
            return;
        }
        unsafe { GEOSCoordSeq_destroy_r(self.get_raw_context(), self.as_raw_mut()) };
    }
}

impl<'a> Clone for CoordSeq<'a> {
    /// Also pass the context to the newly created `CoordSeq`.
    fn clone(&self) -> CoordSeq<'a> {
        let ptr = unsafe { GEOSCoordSeq_clone_r(self.get_raw_context(), self.as_raw()) };
        if ptr.is_null() {
            panic!("Couldn't clone CoordSeq...");
        }
        CoordSeq {
            ptr: PtrWrap(ptr),
            context: self.clone_context(),
            nb_dimensions: self.nb_dimensions,
            nb_lines: self.nb_lines,
        }
    }
}

impl<'a> ContextInteractions<'a> for CoordSeq<'a> {
    /// Set the context handle to the `CoordSeq`.
    ///
    /// ```
    /// use geos::{ContextInteractions, CoordDimensions, CoordSeq, ContextHandle};
    ///
    /// let context_handle = ContextHandle::init().expect("invalid init");
    /// context_handle.set_notice_message_handler(Some(Box::new(|s| println!("new message: {}", s))));
    /// let mut coord_seq = CoordSeq::new(2, CoordDimensions::TwoD).expect("failed to create CoordSeq");
    /// coord_seq.set_context_handle(context_handle);
    /// ```
    fn set_context_handle(&mut self, context: ContextHandle<'a>) {
        self.context = Arc::new(context);
    }

    /// Get the context handle of the `CoordSeq`.
    ///
    /// ```
    /// use geos::{ContextInteractions, CoordDimensions, CoordSeq};
    ///
    /// let coord_seq = CoordSeq::new(2, CoordDimensions::TwoD).expect("failed to create CoordSeq");
    /// let context = coord_seq.get_context_handle();
    /// context.set_notice_message_handler(Some(Box::new(|s| println!("new message: {}", s))));
    /// ```
    fn get_context_handle(&self) -> &ContextHandle<'a> {
        &self.context
    }
}

impl<'a> AsRaw for CoordSeq<'a> {
    type RawType = GEOSCoordSequence;

    fn as_raw(&self) -> *const Self::RawType {
        *self.ptr
    }
}

impl<'a> AsRawMut for CoordSeq<'a> {
    type RawType = GEOSCoordSequence;

    unsafe fn as_raw_mut_override(&self) -> *mut Self::RawType {
        *self.ptr
    }
}

impl<'a> ContextHandling for CoordSeq<'a> {
    type Context = Arc<ContextHandle<'a>>;

    fn get_raw_context(&self) -> GEOSContextHandle_t {
        self.context.as_raw()
    }

    fn clone_context(&self) -> Arc<ContextHandle<'a>> {
        Arc::clone(&self.context)
    }
}