diff --git a/src/coord_seq.rs b/src/coord_seq.rs index c743d2d..60f4314 100644 --- a/src/coord_seq.rs +++ b/src/coord_seq.rs @@ -30,6 +30,15 @@ pub struct CoordSeq<'a> { nb_lines: usize, } +/// Representation of an immutable GEOS coordinate sequence. Since it's only a view over another +/// GEOS geometry's data, only immutable operations are implemented on it. +pub struct ConstCoordSeq<'a, 'b> { + pub(crate) ptr: PtrWrap<*const GEOSCoordSequence>, + pub(crate) original: &'b Geometry<'a>, + nb_dimensions: usize, + nb_lines: usize, +} + impl<'a> CoordSeq<'a> { /// Creates a new `CoordSeq`. /// @@ -928,3 +937,475 @@ impl<'a> ContextHandling for CoordSeq<'a> { Arc::clone(&self.context) } } + +impl<'a, 'b> ConstCoordSeq<'a, 'b> { + pub(crate) unsafe fn new_from_raw( + ptr: *const GEOSCoordSequence, + original: &'b Geometry<'a>, + size: u32, + dims: u32, + caller: &str, + ) -> GResult> { + if ptr.is_null() { + let extra = if let Some(x) = original.context.get_last_error() { + format!("\nLast error: {x}") + } else { + String::new() + }; + return Err(Error::NoConstructionFromNullPtr(format!( + "CoordSeq::{caller}{extra}", + ))); + } + Ok(ConstCoordSeq { + ptr: PtrWrap(ptr), + original, + nb_dimensions: dims as _, + nb_lines: size as _, + }) + } + + /// 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 { + 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 _) + } + } + + /// 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 { + 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 _) + } + } + + /// 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 { + 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 _) + } + } + + /// Gets the entire `CoordSeq` object as an interleaved buffer. + /// + /// # Parameters: + /// + /// - `dims`: Optionally, the number of dimensions to include in the output buffer. If `None`, + /// will be inferred from the number of dimensions on the geometry. A user may want to + /// override this for performance because GEOS always stores coordinates internally with 3 or + /// 4 dimensions. So copying 2-dimensional GEOS coordinates to a 3-dimensional output buffer + /// is slightly faster (a straight `memcpy`) than copying 2-dimensionalal GEOS coordinates to + /// a 2-dimensional output buffer (iterating over every coordinate and copying only the XY + /// values). + /// + /// # Example + /// + /// ``` + /// use geos::CoordSeq; + /// + /// // Create a two-dimensional `CoordSeq` from a buffer with three points + /// let buffer = vec![0., 1., 2., 3., 4., 5.]; + /// let coords = CoordSeq::new_from_buffer(&buffer, 3, false, false) + /// .expect("failed to create CoordSeq"); + /// + /// // Return an output buffer, inferring the number of coordinates (2) + /// let output_buffer = coords.as_buffer(None) + /// .expect("failed to get buffer"); + /// + /// let expected_output_buffer = vec![0., 1., 2., 3., 4., 5.]; + /// assert_eq!(output_buffer, expected_output_buffer); + /// ``` + /// + /// You can also force GEOS to return a 3D buffer from 2D coordinates + /// + /// ``` + /// use geos::CoordSeq; + /// + /// let buffer = vec![0., 1., 2., 3., 4., 5.]; + /// let coords = CoordSeq::new_from_buffer(&buffer, 3, false, false) + /// .expect("failed to create CoordSeq"); + /// + /// // Return an output buffer, forcing it to be 3-dimensional + /// let output_buffer = coords.as_buffer(Some(3)) + /// .expect("failed to get buffer"); + /// + /// let expected_output_buffer = vec![0., 1., f64::NAN, 2., 3., f64::NAN, 4., 5., f64::NAN]; + /// assert_eq!(output_buffer[0], expected_output_buffer[0]); + /// assert_eq!(output_buffer[3], expected_output_buffer[3]); + /// ``` + /// + /// You can also force GEOS to return a 2D buffer from 3D coordinates + /// + /// ``` + /// use geos::CoordSeq; + /// + /// let buffer = vec![0., 1., 100., 2., 3., 200.]; + /// let coords = CoordSeq::new_from_buffer(&buffer, 2, true, false) + /// .expect("failed to create CoordSeq"); + /// + /// // Return an output buffer, forcing it to be 2-dimensional + /// let output_buffer = coords.as_buffer(Some(2)) + /// .expect("failed to get buffer"); + /// + /// let expected_output_buffer = vec![0., 1., 2., 3.]; + /// assert_eq!(output_buffer, expected_output_buffer); + /// ``` + #[cfg(feature = "v3_10_0")] + pub fn as_buffer(&self, dims: Option) -> GResult> { + let size = self.nb_lines; + let dims = dims.unwrap_or(self.nb_dimensions); + + let has_z = dims >= 3; + let has_m = dims >= 4; + + let mut output_buffer = vec![0.; size * dims]; + unsafe { + GEOSCoordSeq_copyToBuffer_r( + self.get_raw_context(), + self.as_raw(), + output_buffer.as_mut_ptr(), + has_z as _, + has_m as _, + ); + } + + Ok(output_buffer) + } + + /// Gets the entire `CoordSeq` object as individual coordinate arrays. + /// + /// Returns a tuple with four vectors. The first and second vectors correspond to `x` and `y` + /// coordinates. The third corresponds to an optional third dimension if it exists (`z` or + /// `m`). The fourth corresponds to an optional fourth dimension if it exists (`m` in the case + /// of an `XYZM` geometry). + /// + /// # Example + /// + /// ``` + /// use geos::CoordSeq; + /// + /// let buffer = vec![0., 1., 2., 3., 4., 5.]; + /// let coords = CoordSeq::new_from_buffer(&buffer, 3, false, false) + /// .expect("failed to create CoordSeq"); + /// + /// let output_arrays = coords.as_arrays() + /// .expect("failed to get arrays"); + /// + /// assert_eq!(output_arrays.0, vec![0., 2., 4.]); + /// assert_eq!(output_arrays.1, vec![1., 3., 5.]); + /// assert_eq!(output_arrays.2, None); + /// assert_eq!(output_arrays.3, None); + /// ``` + /// + /// If the `CoordSeq` contains three dimensions, the third dimension will be returned as the + /// third value in the tuple: + /// + /// ``` + /// use geos::CoordSeq; + /// + /// let buffer = vec![0., 1., 100., 3., 4., 200., 6., 7., 300.]; + /// let coords = CoordSeq::new_from_buffer(&buffer, 3, true, false) + /// .expect("failed to create CoordSeq"); + /// + /// let output_arrays = coords.as_arrays() + /// .expect("failed to get arrays"); + /// + /// assert_eq!(output_arrays.0, vec![0., 3., 6.]); + /// assert_eq!(output_arrays.1, vec![1., 4., 7.]); + /// assert_eq!(output_arrays.2, Some(vec![100., 200., 300.])); + /// assert_eq!(output_arrays.3, None); + /// ``` + #[cfg(feature = "v3_10_0")] + pub fn as_arrays(&self) -> GResult { + let size = self.nb_lines; + let mut x = vec![0.; size]; + let mut y = vec![0.; size]; + let mut z = if self.nb_dimensions == 3 { + Some(vec![0.; size]) + } else { + None + }; + let mut m = if self.nb_dimensions == 4 { + Some(vec![0.; size]) + } else { + None + }; + + unsafe { + GEOSCoordSeq_copyToArrays_r( + self.get_raw_context(), + self.as_raw(), + x.as_mut_ptr(), + y.as_mut_ptr(), + z.as_mut() + .map(|arr| arr.as_mut_ptr()) + .unwrap_or(std::ptr::null_mut()), + m.as_mut() + .map(|arr| arr.as_mut_ptr()) + .unwrap_or(std::ptr::null_mut()), + ); + } + + Ok((x, y, z, m)) + } + + /// 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 { + 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 { + 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 { + 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 { + 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 { + 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) + } + } + } +} + +unsafe impl<'a, 'b> Send for ConstCoordSeq<'a, 'b> {} +unsafe impl<'a, 'b> Sync for ConstCoordSeq<'a, 'b> {} + +// Assuming that the parent will drop the coord seq? + +// impl<'a> Drop for ConstCoordSeq<'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, 'b> ContextInteractions<'a> for ConstCoordSeq<'a, 'b> { +// /// 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, 'b> AsRaw for ConstCoordSeq<'a, 'b> { + type RawType = GEOSCoordSequence; + + fn as_raw(&self) -> *const Self::RawType { + *self.ptr + } +} + +impl<'a, 'b> ContextHandling for ConstCoordSeq<'a, 'b> { + type Context = Arc>; + + fn get_raw_context(&self) -> GEOSContextHandle_t { + self.original.context.as_raw() + } + + fn clone_context(&self) -> Arc> { + Arc::clone(&self.original.context) + } +} diff --git a/src/geometry.rs b/src/geometry.rs index 417424c..c3c5a0b 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -5,8 +5,8 @@ use crate::functions::*; #[cfg(any(feature = "v3_6_0", feature = "dox"))] use crate::Precision; use crate::{ - AsRaw, AsRawMut, BufferParams, ContextHandle, ContextHandling, ContextInteractions, CoordSeq, - PreparedGeometry, WKTWriter, + AsRaw, AsRawMut, BufferParams, ConstCoordSeq, ContextHandle, ContextHandling, + ContextInteractions, CoordSeq, PreparedGeometry, WKTWriter, }; use c_vec::CVec; use geos_sys::*; @@ -33,8 +33,8 @@ pub struct Geometry<'a> { pub(crate) context: Arc>, } -// Representation of a GEOS geometry. Since it's only a view over another GEOS geometry data, -/// only not mutable operations are implemented on it. +/// Representation of an immutable GEOS geometry. Since it's only a view over another GEOS +/// geometry's data, only immutable operations are implemented on it. /// /// # Example /// @@ -117,7 +117,7 @@ pub trait Geom<'a>: /// assert_eq!(coord_seq.get_x(0), Ok(2.)); /// assert_eq!(coord_seq.get_y(0), Ok(3.)); /// ``` - fn get_coord_seq(&self) -> GResult>; + fn get_coord_seq<'c>(&'c self) -> GResult>; /// Returns the area of the geometry. Units are specified by the SRID of the given geometry. /// /// # Example @@ -1446,7 +1446,7 @@ impl<'a$(, $lt)?> Geom<'a> for $ty_name<'a$(, $lt)?> { } } - fn get_coord_seq(&self) -> GResult> { + fn get_coord_seq<'c>(&'c self) -> GResult> { let type_geom = self.geometry_type(); match type_geom { GeometryTypes::Point | GeometryTypes::LineString | GeometryTypes::LinearRing => unsafe { @@ -1461,7 +1461,7 @@ impl<'a$(, $lt)?> Geom<'a> for $ty_name<'a$(, $lt)?> { if GEOSCoordSeq_getDimensions_r(self.get_raw_context(), coord, &mut dims) == 0 { return Err(Error::GenericError("GEOSCoordSeq_getDimensions_r failed".to_owned())); } - CoordSeq::new_from_raw(t, self.clone_context(), size, dims, "get_coord_seq") + ConstCoordSeq::new_from_raw(t, self$(.$field)?, size, dims, "get_coord_seq") }, _ => Err(Error::ImpossibleOperation( "Geometry must be a Point, LineString or LinearRing to extract its coordinates" diff --git a/src/lib.rs b/src/lib.rs index eea6504..54ce406 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ pub(crate) mod functions; pub use buffer_params::{BufferParams, BufferParamsBuilder}; pub use context_handle::{ContextHandle, HandlerCallback}; -pub use coord_seq::CoordSeq; +pub use coord_seq::{ConstCoordSeq, CoordSeq}; #[cfg(any(feature = "v3_6_0", feature = "dox"))] pub use enums::Precision; pub use enums::{