use crate::context_handle::with_context;
use crate::enums::*;
use crate::error::{Error, GResult};
use crate::functions::*;
use crate::traits::{as_raw_impl, as_raw_mut_impl};
#[cfg(feature = "v3_10_0")]
use crate::GeoJSONWriter;
#[cfg(feature = "v3_10_0")]
use crate::MakeValidParams;
#[cfg(feature = "v3_6_0")]
use crate::Precision;
use crate::{AsRaw, AsRawMut, BufferParams, CoordSeq, PreparedGeometry, WKTWriter};
use geos_sys::*;
use std::borrow::Borrow;
use std::ffi::CString;
use std::marker::PhantomData;
use std::ptr::NonNull;
use std::{self, str};

/// Representation of a GEOS geometry.
///
/// # Example
///
/// ```
/// use geos::{Geom, Geometry};
///
/// let point_geom = Geometry::new_from_wkt("POINT (2.5 3.5)")?;
/// assert_eq!(point_geom.get_x()?, 2.5);
/// assert_eq!(point_geom.get_y()?, 3.5);
/// # Ok::<(), geos::Error>(())
/// ```
pub struct Geometry {
    pub(crate) ptr: NonNull<GEOSGeometry>,
}

// Representation of a GEOS geometry. Since it's only a view over another GEOS geometry data,
/// only not mutable operations are implemented on it.
///
/// # Example
///
/// ```
/// use geos::{Geom, Geometry};
///
/// let geom = Geometry::new_from_wkt(
///     "POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))",
/// )?;
/// let point_geom = geom.get_interior_ring_n(0)?;
/// # Ok::<(), geos::Error>(())
/// ```
pub struct ConstGeometry<'a> {
    pub(crate) ptr: NonNull<GEOSGeometry>,
    phantom: PhantomData<&'a Geometry>,
}

unsafe impl Send for Geometry {}
unsafe impl Sync for Geometry {}

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

impl Geom for Geometry {}
impl Geom for ConstGeometry<'_> {}

pub trait Geom: AsRaw<RawType = GEOSGeometry> + Sized + Send + Sync {
    /// Returns the type of the geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POLYGON((0 0, 1 1, 1 2, 1 1, 0 0))")?;
    /// assert_eq!(geom.get_type()?, "Polygon");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_type(&self) -> GResult<String> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeomType_r(ctx.as_raw(), self.as_raw()))?;
            managed_string(ptr, ctx)
        })
    }

    fn geometry_type(&self) -> GResult<GeometryTypes> {
        with_context(|ctx| unsafe {
            let geom_type = errcheck!(-1, GEOSGeomTypeId_r(ctx.as_raw(), self.as_raw()))?;
            GeometryTypes::try_from(geom_type)
        })
    }

    /// Checks if the geometry is valid.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POLYGON((0 0, 1 1, 1 2, 1 1, 0 0))")?;
    /// assert_eq!(geom.is_valid()?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn is_valid(&self) -> GResult<bool> {
        with_context(|ctx| unsafe { predicate!(GEOSisValid_r(ctx.as_raw(), self.as_raw())) })
    }

    /// Returns an explanation on why the geometry is invalid.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// // Bowtie polygon with self-intersection
    /// let geom = Geometry::new_from_wkt("POLYGON((0 0, 2 2, 2 0, 0 2, 0 0))")?;
    /// assert_eq!(geom.is_valid_reason()?, "Self-intersection[1 1]".to_owned(),);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn is_valid_reason(&self) -> GResult<String> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSisValidReason_r(ctx.as_raw(), self.as_raw()))?;
            managed_string(ptr, ctx)
        })
    }

    /// Get the underlying geos `CoordSeq` object from the geometry
    ///
    /// Note: this clones the underlying `CoordSeq` to avoid double free
    /// (because `CoordSeq` handles the object ptr and the `CoordSeq` is still owned by the geos
    /// geometry) if this method's performance becomes a bottleneck, feel free to open an issue,
    /// we could skip this clone with cleaner code.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT (2 3)")?;
    /// let coord_seq = geom.get_coord_seq()?;
    ///
    /// assert_eq!(coord_seq.get_x(0)?, 2.);
    /// assert_eq!(coord_seq.get_y(0)?, 3.);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_coord_seq(&self) -> GResult<CoordSeq> {
        with_context(|ctx| unsafe {
            let coords = nullcheck!(GEOSGeom_getCoordSeq_r(ctx.as_raw(), self.as_raw()))?;
            let coords = nullcheck!(GEOSCoordSeq_clone_r(ctx.as_raw(), coords.as_ptr()))?;

            let mut size = 0;
            errcheck!(GEOSCoordSeq_getSize_r(
                ctx.as_raw(),
                coords.as_ptr(),
                &mut size
            ))?;

            #[cfg(not(feature = "v3_14_0"))]
            {
                let mut dims = 0;
                errcheck!(GEOSCoordSeq_getDimensions_r(
                    ctx.as_raw(),
                    coords.as_ptr(),
                    &mut dims
                ))?;
                Ok(CoordSeq::new_from_raw(coords, size, dims.try_into()?))
            }
            #[cfg(feature = "v3_14_0")]
            {
                let has_z = predicate!(GEOSCoordSeq_hasZ_r(ctx.as_raw(), coords.as_ptr()))?;
                let has_m = predicate!(GEOSCoordSeq_hasM_r(ctx.as_raw(), coords.as_ptr()))?;

                Ok(CoordSeq::new_from_raw(
                    coords,
                    size,
                    CoordType::try_from((has_z, has_m))?,
                ))
            }
        })
    }

    /// Returns the area of the geometry. Units are specified by the SRID of the given geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0))")?;
    /// assert_eq!(geom1.area()?, 60.);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn area(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut n = 0.;
            errcheck!(GEOSArea_r(ctx.as_raw(), self.as_raw(), &mut n))?;
            Ok(n)
        })
    }

    /// Returns a WKT representation of the geometry. It defaults to 2 dimensions output. Use
    /// [`WKTWriter`] type directly if you want more control.
    ///
    /// # Examples
    ///
    /// ```
    /// use geos::{Geom, Geometry, WKTWriter};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// assert_eq!(point_geom.to_wkt()?, "POINT (2.5 2.5)");
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT Z (2.5 2.5 3)")?;
    /// assert_eq!(point_geom.to_wkt()?, "POINT Z (2.5 2.5 3)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn to_wkt(&self) -> GResult<String> {
        WKTWriter::new()?.write(self)
    }

    /// Returns a WKT representation of the geometry with the given `precision`. It is a wrapper
    /// around [`WKTWriter::set_rounding_precision`].
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry, WKTWriter};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.43 2.56)")?;
    /// assert_eq!(point_geom.to_wkt_precision(1)?, "POINT (2.4 2.6)");
    ///
    /// // It is a wrapper around:
    /// let mut writer = WKTWriter::new()?;
    /// writer.set_rounding_precision(1);
    /// assert_eq!(writer.write(&point_geom)?, "POINT (2.4 2.6)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(not(all(doctest, feature = "tests", not(feature = "v3_10_0"))))]
    fn to_wkt_precision(&self, precision: u32) -> GResult<String> {
        let mut writer = WKTWriter::new()?;
        writer.set_rounding_precision(precision);
        writer.write(self)
    }

    /// Returns `true` if the geometry is a ring.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let circle = Geometry::new_from_wkt("LINESTRING(0 0, 0 1, 1 1, 0 0)")?;
    /// assert_eq!(circle.is_ring()?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn is_ring(&self) -> GResult<bool> {
        with_context(|ctx| unsafe { predicate!(GEOSisRing_r(ctx.as_raw(), self.as_raw())) })
    }

    /// Returns `true` if `self` shares any portion of space with `other`. So if any of this is
    /// `true`:
    ///
    /// * `self` overlaps `other`
    /// * `self` touches `other`
    /// * `self` is within `other`
    ///
    /// Then `intersects` will return `true` as well.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT(0 0)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING(2 0, 0 2)")?;
    /// let geom3 = Geometry::new_from_wkt("LINESTRING(0 0, 0 2)")?;
    ///
    /// assert_eq!(geom1.intersects(&geom2)?, false);
    /// assert_eq!(geom1.intersects(&geom3)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn intersects<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSIntersects_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw()
            ))
        })
    }

    /// Returns `true` if `self` and `other` have at least one interior into each other.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("LINESTRING(1 1,2 2)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING(2 1,1 2)")?;
    ///
    /// assert_eq!(geom1.crosses(&geom2)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn crosses<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSCrosses_r(ctx.as_raw(), self.as_raw(), other.as_raw()))
        })
    }

    /// Returns `true` if `self` doesn't:
    ///
    /// * Overlap `other`
    /// * Touch `other`
    /// * Is within `other`
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT(0 0)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING(2 0, 0 2)")?;
    /// let geom3 = Geometry::new_from_wkt("LINESTRING(0 0, 0 2)")?;
    ///
    /// assert_eq!(geom1.disjoint(&geom2)?, true);
    /// assert_eq!(geom1.disjoint(&geom3)?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn disjoint<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSDisjoint_r(ctx.as_raw(), self.as_raw(), other.as_raw()))
        })
    }

    /// Returns `true` if the only points in common between `self` and `other` lie in the union of
    /// the boundaries of `self` and `other`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("LINESTRING(0 0, 1 1, 0 2)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT(1 1)")?;
    ///
    /// assert_eq!(geom1.touches(&geom2)?, false);
    ///
    /// let geom2 = Geometry::new_from_wkt("POINT(0 2)")?;
    ///
    /// assert_eq!(geom1.touches(&geom2)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn touches<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSTouches_r(ctx.as_raw(), self.as_raw(), other.as_raw()))
        })
    }

    /// Returns `true` if `self` spatially overlaps `other`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT(1 0.5)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING(1 0, 1 1, 3 5)")?;
    ///
    /// assert_eq!(geom1.overlaps(&geom2)?, false);
    ///
    /// let geom1 = geom1.buffer(3., 8)?;
    /// let geom2 = geom2.buffer(0.5, 8)?;
    ///
    /// assert_eq!(geom1.overlaps(&geom2)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn overlaps<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSOverlaps_r(ctx.as_raw(), self.as_raw(), other.as_raw()))
        })
    }

    /// Returns `true` if `self` is completely inside `other`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT(50 50)")?;
    /// let small_geom = geom.buffer(20., 8)?;
    /// let big_geom = geom.buffer(40., 8)?;
    ///
    /// assert_eq!(small_geom.within(&small_geom)?, true);
    /// assert_eq!(small_geom.within(&big_geom)?, true);
    /// assert_eq!(big_geom.within(&small_geom)?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn within<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSWithin_r(ctx.as_raw(), self.as_raw(), other.as_raw()))
        })
    }

    /// Checks if the two [`Geometry`] objects are equal.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (3.8 3.8)")?;
    /// let geom3 = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    ///
    /// assert_eq!(geom1.equals(&geom2)?, false);
    /// assert_eq!(geom1.equals(&geom3)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    ///
    /// Note that you can also use method through the `PartialEq` trait:
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (3.8 3.8)")?;
    /// let geom3 = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    ///
    /// assert!(geom1 != geom2);
    /// assert!(geom1 == geom3);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn equals<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSEquals_r(ctx.as_raw(), self.as_raw(), other.as_raw()))
        })
    }

    /// Checks if the two [`Geometry`] objects are exactly equal.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (3.8 3.8)")?;
    /// let geom3 = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    ///
    /// assert_eq!(geom1.equals_exact(&geom2, 0.1)?, false);
    /// assert_eq!(geom1.equals_exact(&geom3, 0.1)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn equals_exact<G: Geom>(&self, other: &G, precision: f64) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSEqualsExact_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                precision
            ))
        })
    }

    #[cfg(feature = "v3_12_0")]
    fn equals_identical<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSEqualsIdentical_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw()
            ))
        })
    }

    /// Returns `true` if no point of `other` is outside of `self`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT (1 2)")?;
    /// let little_geom = geom.buffer(10., 8)?;
    /// let big_geom = geom.buffer(20., 8)?;
    ///
    /// assert_eq!(little_geom.covers(&big_geom)?, false);
    /// assert_eq!(big_geom.covers(&little_geom)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn covers<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSCovers_r(ctx.as_raw(), self.as_raw(), other.as_raw()))
        })
    }

    /// Returns `true` if no point of `self` is outside of `other`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT (1 2)")?;
    /// let little_geom = geom.buffer(10., 8)?;
    /// let big_geom = geom.buffer(20., 8)?;
    ///
    /// assert_eq!(little_geom.covered_by(&big_geom)?, true);
    /// assert_eq!(big_geom.covered_by(&little_geom)?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn covered_by<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSCoveredBy_r(ctx.as_raw(), self.as_raw(), other.as_raw()))
        })
    }

    /// Returns `true` if no points of the `other` geometry is outside the exterior of `self`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0))")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    ///
    /// assert_eq!(geom1.contains(&geom2)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn contains<G: Geom>(&self, other: &G) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSContains_r(ctx.as_raw(), self.as_raw(), other.as_raw()))
        })
    }

    fn relate<G: Geom>(&self, other: &G) -> GResult<String> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSRelate_r(ctx.as_raw(), self.as_raw(), other.as_raw()))?;
            managed_string(ptr, ctx)
        })
    }

    fn relate_pattern<G: Geom>(&self, other: &G, pattern: &str) -> GResult<bool> {
        with_context(|ctx| unsafe {
            let pattern = CString::new(pattern)
                .map_err(|e| Error::GenericError(format!("Conversion to CString failed: {e}")))?;
            predicate!(GEOSRelatePattern_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                pattern.as_ptr()
            ))
        })
    }

    /// Returns a geometry which represents all points whose distance from `self` is less than or
    /// equal to distance.
    ///
    /// You can find nice examples about this in [postgis](https://postgis.net/docs/ST_Buffer.html)
    /// documentation.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT(1 3)")?;
    /// let buffer_geom = geom.buffer(50., 2)?;
    ///
    /// assert_eq!(
    ///     buffer_geom.to_wkt_precision(1)?,
    ///     "POLYGON ((51 3, 36.4 -32.4, 1 -47, -34.4 -32.4, -49 3, -34.4 38.4, 1 53, 36.4 \
    ///                       38.4, 51 3))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(not(all(doctest, feature = "tests", not(feature = "v3_10_0"))))]
    fn buffer(&self, width: f64, quadsegs: i32) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSBuffer_r(
                ctx.as_raw(),
                self.as_raw(),
                width,
                quadsegs as _
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns a geometry which represents all points whose distance from `self` is less than or
    /// equal to distance.
    ///
    /// The explicit `buffer_params` argument passing is more efficient than
    /// the otherwise identical (except for the `single_sided` option is missing)
    /// [`buffer_with_style`](crate::Geom::buffer_with_style) method when the same [`BufferParams`]
    /// is reused.
    ///
    /// You can find nice examples and details about the [`BufferParams`] options in this
    /// [postgis](https://postgis.net/docs/ST_Buffer.html) documentation.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{BufferParams, CapStyle, Geom, Geometry, JoinStyle};
    ///
    /// let geom = Geometry::new_from_wkt("POINT(1 3)")?;
    /// let params = BufferParams::builder()
    ///     .end_cap_style(CapStyle::Round)
    ///     .join_style(JoinStyle::Round)
    ///     .mitre_limit(5.0)
    ///     .quadrant_segments(8)
    ///     .single_sided(false)
    ///     .build()?;
    /// let buffer_geom = geom.buffer_with_params(2., &params)?;
    ///
    /// assert_eq!(
    ///     buffer_geom.to_wkt_precision(1)?,
    ///     "POLYGON ((3 3, 3 2.6, 2.8 2.2, 2.7 1.9, 2.4 1.6, 2.1 1.3, 1.8 1.2, 1.4 1, 1 1, \
    ///              0.6 1, 0.2 1.2, -0.1 1.3, -0.4 1.6, -0.7 1.9, -0.8 2.2, -1 2.6, -1 3, -1 3.4, \
    ///              -0.8 3.8, -0.7 4.1, -0.4 4.4, -0.1 4.7, 0.2 4.8, 0.6 5, 1 5, 1.4 5, 1.8 4.8, \
    ///              2.1 4.7, 2.4 4.4, 2.7 4.1, 2.8 3.8, 3 3.4, 3 3))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(not(all(doctest, feature = "tests", not(feature = "v3_10_0"))))]
    fn buffer_with_params(&self, width: f64, buffer_params: &BufferParams) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSBufferWithParams_r(
                ctx.as_raw(),
                self.as_raw(),
                buffer_params.as_raw(),
                width
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns a geometry which represents all points whose distance from `self` is less than or
    /// equal to distance.
    ///
    /// If the same parameters are used many times it's more efficient to use the
    /// [`buffer_with_params`](crate::Geom::buffer_with_params) operation.
    ///
    /// You can find nice examples and details about the options in this
    /// [postgis](https://postgis.net/docs/ST_Buffer.html) documentation.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CapStyle, Geom, Geometry, JoinStyle};
    ///
    /// let geom = Geometry::new_from_wkt("POINT(1 3)")?;
    /// let buffer_geom = geom.buffer_with_style(2., 8, CapStyle::Round, JoinStyle::Round, 5.)?;
    /// assert_eq!(
    ///     buffer_geom.to_wkt_precision(1)?,
    ///     "POLYGON ((3 3, 3 2.6, 2.8 2.2, 2.7 1.9, 2.4 1.6, 2.1 1.3, 1.8 1.2, 1.4 1, 1 1, \
    ///             0.6 1, 0.2 1.2, -0.1 1.3, -0.4 1.6, -0.7 1.9, -0.8 2.2, -1 2.6, -1 3, -1 3.4, \
    ///             -0.8 3.8, -0.7 4.1, -0.4 4.4, -0.1 4.7, 0.2 4.8, 0.6 5, 1 5, 1.4 5, 1.8 4.8, \
    ///             2.1 4.7, 2.4 4.4, 2.7 4.1, 2.8 3.8, 3 3.4, 3 3))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(not(all(doctest, feature = "tests", not(feature = "v3_10_0"))))]
    fn buffer_with_style(
        &self,
        width: f64,
        quadsegs: i32,
        end_cap_style: CapStyle,
        join_style: JoinStyle,
        mitre_limit: f64,
    ) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSBufferWithStyle_r(
                ctx.as_raw(),
                self.as_raw(),
                width,
                quadsegs,
                end_cap_style.into(),
                join_style.into(),
                mitre_limit
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns `true` if the given geometry is empty.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::create_empty_polygon()?;
    /// assert_eq!(geom.is_empty()?, true);
    ///
    /// let geom = Geometry::new_from_wkt("POLYGON EMPTY")?;
    /// assert_eq!(geom.is_empty()?, true);
    ///
    /// let geom = Geometry::new_from_wkt("POINT(1 3)")?;
    /// assert_eq!(geom.is_empty()?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn is_empty(&self) -> GResult<bool> {
        with_context(|ctx| unsafe { predicate!(GEOSisEmpty_r(ctx.as_raw(), self.as_raw())) })
    }

    /// Returns true if the given geometry has no anomalous geometric points, such as self
    /// intersection or self tangency.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// assert_eq!(geom.is_simple()?, true);
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING(1 1,2 2,2 3.5,1 3,1 2,2 1)")?;
    /// assert_eq!(geom.is_simple()?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn is_simple(&self) -> GResult<bool> {
        with_context(|ctx| unsafe { predicate!(GEOSisSimple_r(ctx.as_raw(), self.as_raw())) })
    }

    /// Returns a geometry which represents part of `self` that doesn't intersect with `other`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("LINESTRING(50 100, 50 200)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING(50 50, 50 150)")?;
    ///
    /// let difference_geom = geom1.difference(&geom2)?;
    ///
    /// assert_eq!(difference_geom.to_wkt()?, "LINESTRING (50 150, 50 200)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn difference<G: Geom>(&self, other: &G) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSDifference_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw()
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_9_0")]
    fn difference_prec<G: Geom>(&self, other: &G, grid_size: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSDifferencePrec_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                grid_size
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the minimum bounding box of the given geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT(1 3)")?;
    /// let envelope_geom = geom.envelope()?;
    ///
    /// assert_eq!(envelope_geom.to_wkt()?, "POINT (1 3)");
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING(0 0, 1 3)")?;
    /// let envelope_geom = geom.envelope()?;
    ///
    /// assert_eq!(
    ///     envelope_geom.to_wkt()?,
    ///     "POLYGON ((0 0, 1 0, 1 3, 0 3, 0 0))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn envelope(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSEnvelope_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns a geometry which represents the parts of `self` and `other` that don't intersect.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("LINESTRING(50 100, 50 200)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING(50 50, 50 150)")?;
    ///
    /// let sym_diff_geom = geom1.sym_difference(&geom2)?;
    ///
    /// assert_eq!(
    ///     sym_diff_geom.to_wkt()?,
    ///     "MULTILINESTRING ((50 150, 50 200), (50 50, 50 100))",
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn sym_difference<G: Geom>(&self, other: &G) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSSymDifference_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw()
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_9_0")]
    fn sym_difference_prec<G: Geom>(&self, other: &G, grid_size: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSSymDifferencePrec_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                grid_size
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Aggregates the given geometry with another one.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("LINESTRING(1 2, 3 4)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING(5 6, 7 8)")?;
    ///
    /// let union_geom = geom1.union(&geom2)?;
    /// assert_eq!(
    ///     union_geom.to_wkt()?,
    ///     "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn union<G: Geom>(&self, other: &G) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSUnion_r(ctx.as_raw(), self.as_raw(), other.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_9_0")]
    fn union_prec<G: Geom>(&self, other: &G, grid_size: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSUnionPrec_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                grid_size
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the geometric center or (equivalently) the center of mass of the given geometry as
    /// a point.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("MULTIPOINT(-1 0, -1 2, -1 3, -1 4, -1 7, 0 1, 0 3, 1 1)")?;
    /// let centroid = geom.get_centroid()?;
    ///
    /// assert_eq!(centroid.to_wkt()?, "POINT (-0.5 2.625)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_centroid(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGetCentroid_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Documentation from [postgis](https://postgis.net/docs/ST_UnaryUnion.html):
    ///
    /// > Unlike ST_Union, ST_UnaryUnion does dissolve boundaries between components of a
    /// > multipolygon (invalid) and does perform union between the components of a
    /// > geometrycollection. Each components of the input geometry is assumed to be valid, so you
    /// > won't get a valid multipolygon out of a bow-tie polygon (invalid).
    /// >
    /// > You may use this function to node a set of linestrings. You may mix ST_UnaryUnion with
    /// > ST_Collect to fine-tune how many geometries at once you want to dissolve to be nice on
    /// > both memory size and CPU time, finding the balance between ST_Union and ST_MemUnion.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0))")?;
    /// let geom2 = Geometry::new_from_wkt("POLYGON((1 1, 2 1, 2 5, 1 5, 1 1))")?;
    ///
    /// let geom = Geometry::create_multipolygon(vec![geom1, geom2])?;
    ///
    /// let mut union_geom = geom.unary_union()?;
    /// union_geom.normalize()?;
    ///
    /// assert_eq!(
    ///     union_geom.to_wkt()?,
    ///     "POLYGON ((0 0, 0 6, 10 6, 10 0, 0 0))",
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn unary_union(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSUnaryUnion_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_9_0")]
    fn unary_union_prec(&self, grid_size: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSUnaryUnionPrec_r(ctx.as_raw(), self.as_raw(), grid_size))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_8_0")]
    fn coverage_union(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSCoverageUnion_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_12_0")]
    fn disjoint_subset_union(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSDisjointSubsetUnion_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Create a voronoi diagram.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let input = Geometry::new_from_wkt("MULTIPOINT(2 2, 4 2)")?;
    /// let mut expected = Geometry::new_from_wkt(
    ///     "GEOMETRYCOLLECTION(POLYGON((0 0, 0 4, 3 4, 3 0, 0 0)), POLYGON((6 4, 6 0, 3 0, 3 4, 6 4)))")?;
    ///
    /// let mut voronoi = input.voronoi(None::<&Geometry>, 0., false)?;
    ///
    /// expected.normalize()?;
    /// voronoi.normalize()?;
    ///
    /// assert_eq!(expected.equals(&voronoi)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn voronoi<G: Geom>(
        &self,
        envelope: Option<&G>,
        tolerance: f64,
        only_edges: bool,
    ) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSVoronoiDiagram_r(
                ctx.as_raw(),
                self.as_raw(),
                envelope.map_or(std::ptr::null_mut(), AsRaw::as_raw),
                tolerance,
                only_edges.into(),
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns a geometry representing the intersection between `self` and `other`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let mut geom1 = Geometry::new_from_wkt("POINT(0 0)")?;
    /// let mut geom2 = Geometry::new_from_wkt("LINESTRING(2 0, 0 2)")?;
    ///
    /// let intersection_geom = geom1.intersection(&geom2)?;
    ///
    /// // No intersection.
    /// assert_eq!(intersection_geom.is_empty()?, true);
    ///
    /// // We slightly change the linestring so we have an intersection:
    /// let mut geom2 = Geometry::new_from_wkt("LINESTRING(0 0, 0 2)")?;
    ///
    /// let intersection_geom = geom1.intersection(&geom2)?;
    ///
    /// assert_eq!(intersection_geom.to_wkt()?, "POINT (0 0)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn intersection<G: Geom>(&self, other: &G) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSIntersection_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw()
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_9_0")]
    fn intersection_prec<G: Geom>(&self, other: &G, grid_size: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSIntersectionPrec_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                grid_size
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Documentation from [postgis](https://postgis.net/docs/ST_ConvexHull.html):
    ///
    /// > The convex hull of a geometry represents the minimum convex geometry that encloses all
    /// > geometries within the set.
    /// >
    /// > One can think of the convex hull as the geometry you get by wrapping an elastic band
    /// > around a set of geometries. This is different from a concave hull which is analogous to
    /// > shrink-wrapping your geometries.
    /// >
    /// > It is usually used with MULTI and Geometry Collections. Although it is not an aggregate -
    /// > you can use it in conjunction with ST_Collect to get the convex hull of a set of points.
    /// > ST_ConvexHull(ST_Collect(somepointfield)).
    /// >
    /// > It is often used to determine an affected area based on a set of point observations.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let mut geom1 = Geometry::new_from_wkt("MULTILINESTRING((100 190,10 8), (150 10, 20 30))")?;
    /// let mut geom2 = Geometry::new_from_wkt("MULTIPOINT(50 5, 150 30, 50 10, 10 10)")?;
    ///
    /// let geom = geom1.union(&geom2)?;
    /// let convex_hull_geom = geom.convex_hull()?;
    ///
    /// assert_eq!(
    ///     convex_hull_geom.to_wkt()?,
    ///     "POLYGON ((50 5, 10 8, 10 10, 100 190, 150 30, 150 10, 50 5))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn convex_hull(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSConvexHull_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the closure of the combinatorial boundary of `self`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry, GeometryTypes};
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING(1 1,0 0, -1 1)")?;
    /// let boundary_geom = geom.boundary()?;
    /// assert_eq!(boundary_geom.geometry_type()?, GeometryTypes::MultiPoint);
    /// assert_eq!(boundary_geom.get_num_geometries()?, 2);
    /// assert_eq!(boundary_geom.get_geometry_n(0)?.to_wkt()?, "POINT (1 1)");
    /// assert_eq!(boundary_geom.get_geometry_n(1)?.to_wkt()?, "POINT (-1 1)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn boundary(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSBoundary_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns `true` if `self` has a Z coordinate.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT(1 2 3)")?;
    /// assert_eq!(geom.has_z()?, true);
    ///
    /// let geom = Geometry::new_from_wkt("POINT(1 2)")?;
    /// assert_eq!(geom.has_z()?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn has_z(&self) -> GResult<bool> {
        with_context(|ctx| unsafe { predicate!(GEOSHasZ_r(ctx.as_raw(), self.as_raw())) })
    }

    /// Returns `true` if `self` has a M coordinate.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT(1 2 3 4)")?;
    /// assert_eq!(geom.has_m()?, true);
    ///
    /// let geom = Geometry::new_from_wkt("POINT(1 2 3)")?;
    /// assert_eq!(geom.has_m()?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_12_0")]
    fn has_m(&self) -> GResult<bool> {
        with_context(|ctx| unsafe { predicate!(GEOSHasM_r(ctx.as_raw(), self.as_raw())) })
    }

    /// Returns `true` if start and end point are coincident.
    ///
    /// Only works on `LineString`, `LinearRing`, `CircularString`, `MultiLineString` and `MultiCurve`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING(0 0, 1 1)")?;
    /// assert_eq!(geom.is_closed()?, false);
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING(0 0, 0 1, 1 1, 0 0)")?;
    /// assert_eq!(geom.is_closed()?, true);
    ///
    /// let geom = Geometry::new_from_wkt("MULTILINESTRING((0 0, 0 1, 1 1, 0 0),(0 0, 1 1))")?;
    /// assert_eq!(geom.is_closed()?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn is_closed(&self) -> GResult<bool> {
        with_context(|ctx| unsafe { predicate!(GEOSisClosed_r(ctx.as_raw(), self.as_raw())) })
    }

    /// Returns the length of `self`. The unit depends on the SRID.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING(743238 2967416,743238 2967450)")?;
    ///
    /// assert_eq!(geom.length().map(|x| format!("{:.2}", x))?, "34.00",);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn length(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut length = 0.0;
            errcheck!(GEOSLength_r(ctx.as_raw(), self.as_raw(), &mut length))?;
            Ok(length)
        })
    }

    /// Returns the distance between `self` and `other`. The unit depends on the SRID.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT (1 2)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (2 2)")?;
    ///
    /// assert_eq!(geom1.distance(&geom2).map(|x| format!("{:.2}", x))?, "1.00");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn distance<G: Geom>(&self, other: &G) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut distance = 0.0;
            errcheck!(GEOSDistance_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                &mut distance
            ))?;
            Ok(distance)
        })
    }

    /// Returns `true` if the distance between `self` and `other` is shorter than `distance`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT (1 2)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (2 2)")?;
    /// let geom3 = Geometry::new_from_wkt("POINT (3 2)")?;
    ///
    /// assert_eq!(geom1.dwithin(&geom2, 1.0)?, true);
    /// assert_eq!(geom1.dwithin(&geom3, 1.0)?, false);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_10_0")]
    fn dwithin<G: Geom>(&self, other: &G, distance: f64) -> GResult<bool> {
        with_context(|ctx| unsafe {
            predicate!(GEOSDistanceWithin_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                distance
            ))
        })
    }

    /// Returns the indexed distance between `self` and `other`. The unit depends on the SRID.
    ///
    /// Available using the `v3_7_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT (1 2)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (2 2)")?;
    ///
    /// assert_eq!(
    ///     geom1
    ///         .distance_indexed(&geom2)
    ///         .map(|x| format!("{:.2}", x))?,
    ///     "1.00"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_7_0")]
    fn distance_indexed<G: Geom>(&self, other: &G) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut distance = 0.0;
            errcheck!(GEOSDistanceIndexed_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                &mut distance
            ))?;
            Ok(distance)
        })
    }

    /// Returns the hausdorff distance between `self` and `other`. The unit depends on the SRID.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT (1 2)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (2 2)")?;
    ///
    /// assert_eq!(
    ///     geom1
    ///         .hausdorff_distance(&geom2)
    ///         .map(|x| format!("{:.2}", x))?,
    ///     "1.00"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn hausdorff_distance<G: Geom>(&self, other: &G) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut distance = 0.0;
            errcheck!(GEOSHausdorffDistance_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                &mut distance
            ))?;
            Ok(distance)
        })
    }

    /// Returns the hausdorff distance between `self` and `other`. The unit depends on the SRID.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT (1 2)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (2 2)")?;
    ///
    /// assert_eq!(
    ///     geom1
    ///         .hausdorff_distance_densify(&geom2, 1.)
    ///         .map(|x| format!("{:.2}", x))?,
    ///     "1.00"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn hausdorff_distance_densify<G: Geom>(&self, other: &G, distance_frac: f64) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut distance = 0.0;
            errcheck!(GEOSHausdorffDistanceDensify_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                distance_frac,
                &mut distance
            ))?;
            Ok(distance)
        })
    }

    /// Returns the frechet distance between `self` and `other`. The unit depends on the SRID.
    ///
    /// Available using the `v3_7_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("LINESTRING (0 0, 100 0)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING (0 0, 50 50, 100 0)")?;
    ///
    /// assert_eq!(
    ///     geom1
    ///         .frechet_distance(&geom2)
    ///         .map(|x| format!("{:.2}", x))?,
    ///     "70.71"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_7_0")]
    fn frechet_distance<G: Geom>(&self, other: &G) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut distance = 0.0;
            errcheck!(GEOSFrechetDistance_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                &mut distance
            ))?;
            Ok(distance)
        })
    }

    /// Returns the frechet distance between `self` and `other`. The unit depends on the SRID.
    ///
    /// Available using the `v3_7_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("LINESTRING (0 0, 100 0)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING (0 0, 50 50, 100 0)")?;
    ///
    /// assert_eq!(
    ///     geom1
    ///         .frechet_distance_densify(&geom2, 1.)
    ///         .map(|x| format!("{:.2}", x))?,
    ///     "70.71"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_7_0")]
    fn frechet_distance_densify<G: Geom>(&self, other: &G, distance_frac: f64) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut distance = 0.0;
            errcheck!(GEOSFrechetDistanceDensify_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                distance_frac,
                &mut distance,
            ))?;
            Ok(distance)
        })
    }

    /// Returns the length of the given geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING (1 2, 3 4, 5 6)")?;
    ///
    /// assert_eq!(geom.get_length().map(|x| format!("{:.2}", x))?, "5.66");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_length(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut length = 0.0;
            errcheck!(GEOSGeomGetLength_r(
                ctx.as_raw(),
                self.as_raw(),
                &mut length
            ))?;
            Ok(length)
        })
    }

    /// Documentation from [postgis](https://postgis.net/docs/ST_Snap.html):
    ///
    /// >  Snaps the vertices and segments of a geometry another Geometry's vertices. A snap
    /// > distance tolerance is used to control where snapping is performed. The result geometry is
    /// > the input geometry with the vertices snapped. If no snapping occurs then the input
    /// > geometry is returned unchanged.
    /// >
    /// > Snapping one geometry to another can improve robustness for overlay operations by
    /// > eliminating nearly-coincident edges (which cause problems during noding and intersection
    /// > calculation).
    /// >
    /// > Too much snapping can result in invalid topology being created, so the number and location
    /// > of snapped vertices is decided using heuristics to determine when it is safe to snap. This
    /// > can result in some potential snaps being omitted, however.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt(
    ///     "MULTIPOLYGON(((26 125, 26 200, 126 200, 126 125, 26 125),
    ///                    (51 150, 101 150, 76 175, 51 150)),
    ///                   ((151 100, 151 200, 176 175, 151 100)))",
    /// )?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING(5 107, 54 84, 101 100)")?;
    ///
    /// let distance = geom1.distance(&geom2)?;
    /// let snap_geom = geom1.snap(&geom2, distance * 1.25)?;
    ///
    /// assert_eq!(
    ///     snap_geom.to_wkt()?,
    ///     "MULTIPOLYGON (((5 107, 26 200, 126 200, 126 125, 101 100, 54 84, 5 107), \
    ///                     (51 150, 101 150, 76 175, 51 150)), \
    ///                    ((151 100, 151 200, 176 175, 151 100)))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn snap<G: Geom>(&self, other: &G, tolerance: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSSnap_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
                tolerance,
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns unique points of `self`.
    fn extract_unique_points(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_extractUniquePoints_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    fn nearest_points<G: Geom>(&self, other: &G) -> GResult<CoordSeq> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSNearestPoints_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw(),
            ))?;

            let mut size = 0;
            errcheck!(GEOSCoordSeq_getSize_r(
                ctx.as_raw(),
                ptr.as_ptr(),
                &mut size
            ))?;

            #[cfg(not(feature = "v3_14_0"))]
            {
                let mut dims = 0;
                errcheck!(GEOSCoordSeq_getDimensions_r(
                    ctx.as_raw(),
                    ptr.as_ptr(),
                    &mut dims
                ))?;
                Ok(CoordSeq::new_from_raw(ptr, size, dims.try_into()?))
            }
            #[cfg(feature = "v3_14_0")]
            {
                let has_z = predicate!(GEOSCoordSeq_hasZ_r(ctx.as_raw(), ptr.as_ptr()))?;
                let has_m = predicate!(GEOSCoordSeq_hasM_r(ctx.as_raw(), ptr.as_ptr()))?;

                Ok(CoordSeq::new_from_raw(
                    ptr,
                    size,
                    CoordType::try_from((has_z, has_m))?,
                ))
            }
        })
    }

    /// Returns the X position. The given `Geometry` must be a `Point`, otherwise it'll fail.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (1.5 2.5 3.5)")?;
    /// assert_eq!(point_geom.get_x()?, 1.5);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_x(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut x = 0.;
            errcheck!(GEOSGeomGetX_r(ctx.as_raw(), self.as_raw(), &mut x))?;
            Ok(x)
        })
    }

    /// Returns the Y position. The given `Geometry` must be a `Point`, otherwise it'll fail.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (1.5 2.5 3.5)")?;
    /// assert_eq!(point_geom.get_y()?, 2.5);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_y(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut x = 0.;
            errcheck!(GEOSGeomGetY_r(ctx.as_raw(), self.as_raw(), &mut x))?;
            Ok(x)
        })
    }

    /// Returns the Z position. The given `Geometry` must be a `Point`, otherwise it'll fail.
    ///
    /// Available using the `v3_7_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5 4.0)")?;
    /// assert_eq!(point_geom.get_z()?, 4.0);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_7_0")]
    fn get_z(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut x = 0.;
            errcheck!(GEOSGeomGetZ_r(ctx.as_raw(), self.as_raw(), &mut x))?;
            Ok(x)
        })
    }

    /// Returns the M position. The given `Geometry` must be a `Point`, otherwise it'll fail.
    ///
    /// Available using the `v3_12_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5 4.0 5.0)")?;
    /// assert_eq!(point_geom.get_m()?, 5.0);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_12_0")]
    fn get_m(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut m = 0.;
            errcheck!(GEOSGeomGetM_r(ctx.as_raw(), self.as_raw(), &mut m))?;
            Ok(m)
        })
    }

    /// Returns the nth point of the given geometry.
    ///
    /// The given `Geometry` must be a `LineString`, `LinearRing` or `CircularString` otherwise it'll fail.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING (1 2, 3 4, 5 6)")?;
    /// let nth_point = geom.get_point_n(1)?;
    ///
    /// assert_eq!(nth_point.to_wkt()?, "POINT (3 4)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_point_n(&self, n: usize) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeomGetPointN_r(ctx.as_raw(), self.as_raw(), n as _))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the start point of `self`.
    ///
    /// The given `Geometry` must be a `LineString`, `LinearRing` or `CircularString` otherwise it'll fail.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING (1 2, 3 4)")?;
    /// let start_point = geom.get_start_point()?;
    ///
    /// assert_eq!(start_point.to_wkt()?, "POINT (1 2)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_start_point(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeomGetStartPoint_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the end point of `self`.
    ///
    /// The given `Geometry` must be a `LineString`, `LinearRing` or `CircularString` otherwise it'll fail.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING (1 2, 3 4)")?;
    /// let end_point = geom.get_end_point()?;
    ///
    /// assert_eq!(end_point.to_wkt()?, "POINT (3 4)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_end_point(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeomGetEndPoint_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the number of points of `self`.
    ///
    /// The given `Geometry` must be a `LineString`, `LinearRing` or `CircularString` otherwise it'll fail.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING (1 2, 3 4)")?;
    ///
    /// assert_eq!(geom.get_num_points()?, 2);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_num_points(&self) -> GResult<usize> {
        with_context(|ctx| unsafe {
            let ret = errcheck!(-1, GEOSGeomGetNumPoints_r(ctx.as_raw(), self.as_raw()))?;
            Ok(ret as _)
        })
    }

    /// Returns the number of interior rings.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt(
    ///     "POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))",
    /// )?;
    ///
    /// assert_eq!(geom.get_num_interior_rings()?, 2);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_num_interior_rings(&self) -> GResult<usize> {
        with_context(|ctx| unsafe {
            let ret = errcheck!(-1, GEOSGetNumInteriorRings_r(ctx.as_raw(), self.as_raw()))?;
            Ok(ret as _)
        })
    }

    /// Returns the number of coordinates inside `self`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0))")?;
    ///
    /// assert_eq!(geom.get_num_coordinates()?, 5);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_num_coordinates(&self) -> GResult<usize> {
        with_context(|ctx| unsafe {
            let ret = errcheck!(-1, GEOSGetNumCoordinates_r(ctx.as_raw(), self.as_raw()))?;
            Ok(ret as _)
        })
    }

    /// Returns the number of dimensions used in `self`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{DimensionType, Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0))")?;
    ///
    /// assert_eq!(geom.get_dimension()?, DimensionType::Surface);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_dimension(&self) -> GResult<DimensionType> {
        with_context(|ctx| unsafe {
            // Need to skip errcheck as 0 is a valid return values
            // TODO: file a bug report to GEOS
            let ret = GEOSGeom_getDimensions_r(ctx.as_raw(), self.as_raw());
            DimensionType::try_from(ret)
        })
    }

    /// Return in which coordinate dimension the geometry is.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5 4.0)")?;
    /// assert_eq!(
    ///     point_geom.get_coordinate_dimension()?,
    ///     CoordDimensions::ThreeD
    /// );
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 4.0)")?;
    /// assert_eq!(
    ///     point_geom.get_coordinate_dimension()?,
    ///     CoordDimensions::TwoD
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_coordinate_dimension(&self) -> GResult<CoordDimensions> {
        with_context(|ctx| unsafe {
            let ret = errcheck!(GEOSGeom_getCoordinateDimension_r(
                ctx.as_raw(),
                self.as_raw(),
            ))?;
            CoordDimensions::try_from(ret)
        })
    }

    /// Return the coordinate type of the geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordType, Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT M (2.5 2.5 4.0)")?;
    /// assert_eq!(point_geom.get_coordinate_type()?, CoordType::XYM);
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT Z (2.5 2.5 4.0)")?;
    /// assert_eq!(point_geom.get_coordinate_type()?, CoordType::XYZ);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(any(feature = "v3_12_0", feature = "dox"))]
    fn get_coordinate_type(&self) -> GResult<CoordType> {
        with_context(|ctx| unsafe {
            let has_z = predicate!(GEOSHasZ_r(ctx.as_raw(), self.as_raw()))?;
            let has_m = predicate!(GEOSHasM_r(ctx.as_raw(), self.as_raw()))?;
            (has_z, has_m).try_into()
        })
    }

    /// This function attempts to return a valid representation of `self`.
    ///
    /// Available using the `v3_8_0` feature.
    #[cfg(feature = "v3_8_0")]
    fn make_valid(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSMakeValid_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Attempts to return a valid representation of `self` using the provided parameters.
    ///
    /// This allows control over the validation method and whether to preserve collapsed geometries.
    ///
    /// Available using the `v3_10_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry, MakeValidMethod, MakeValidParams};
    ///
    /// // Bow-tie polygon (self-intersecting, invalid)
    /// let geom = Geometry::new_from_wkt("POLYGON((0 0, 1 1, 0 1, 1 0, 0 0))")?;
    ///
    /// assert_eq!(geom.is_valid()?, false);
    ///
    /// let params = MakeValidParams::builder()
    ///     .method(MakeValidMethod::Structure)
    ///     .keep_collapsed(false)
    ///     .build()?;
    ///
    /// let valid_geom = geom.make_valid_with_params(&params)?;
    ///
    /// assert_eq!(valid_geom.is_valid()?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_10_0")]
    fn make_valid_with_params(&self, params: &MakeValidParams) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSMakeValidWithParams_r(
                ctx.as_raw(),
                self.as_raw(),
                params.as_raw(),
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the number of geometries.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom =
    ///     Geometry::new_from_wkt("LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)")?;
    /// assert_eq!(geom.get_num_geometries()?, 1);
    ///
    /// let geom = Geometry::new_from_wkt(
    ///     "GEOMETRYCOLLECTION(MULTIPOINT(-2 3 , -2 2), \
    ///                         LINESTRING(5 5 ,10 10), \
    ///                         POLYGON((-7 4.2,-7.1 5,-7.1 4.3,-7 4.2)))",
    /// )?;
    /// assert_eq!(geom.get_num_geometries()?, 3);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_num_geometries(&self) -> GResult<usize> {
        with_context(|ctx| unsafe {
            let ret = errcheck!(-1, GEOSGetNumGeometries_r(ctx.as_raw(), self.as_raw()))?;
            Ok(ret as _)
        })
    }

    /// Get SRID of `self`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let mut point_geom = Geometry::new_from_wkt("POINT (2.5 2.5 4.0)")?;
    /// point_geom.set_srid(4326);
    /// assert_eq!(point_geom.get_srid()?, 4326);
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_srid(&self) -> GResult<libc::c_int> {
        with_context(|ctx| unsafe {
            // No need to wrap this one, as 0 is a valid SRID
            let ret = GEOSGetSRID_r(ctx.as_raw(), self.as_raw());
            Ok(ret as _)
        })
    }

    /// Returns the precision of `self`.
    ///
    /// Available using the `v3_6_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5 4.0)")?;
    /// assert_eq!(
    ///     point_geom.get_precision().map(|x| format!("{:.2}", x))?,
    ///     "0.00"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_6_0")]
    fn get_precision(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            errcheck!(-1.0, GEOSGeom_getPrecision_r(ctx.as_raw(), self.as_raw()))
        })
    }

    /// Returns the precision of `self`.
    ///
    /// Available using the `v3_6_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry, Precision};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5 4.0)")?;
    ///
    /// point_geom.set_precision(1., Precision::KeepCollapsed);
    /// assert_eq!(
    ///     point_geom.get_precision().map(|x| format!("{:.2}", x))?,
    ///     "0.00"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_6_0")]
    fn set_precision(&self, grid_size: f64, flags: Precision) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_setPrecision_r(
                ctx.as_raw(),
                self.as_raw(),
                grid_size,
                flags.into(),
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the biggest X of the geometry.
    ///
    /// Available using the `v3_7_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let line = Geometry::new_from_wkt("LINESTRING(1 3 4, 5 6 7)")?;
    /// assert_eq!(line.get_x_max()?, 5.);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_7_0")]
    fn get_x_max(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut value = 0.0;
            errcheck!(GEOSGeom_getXMax_r(ctx.as_raw(), self.as_raw(), &mut value))?;
            Ok(value)
        })
    }

    /// Returns the smallest X of the geometry.
    ///
    /// Available using the `v3_7_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let line = Geometry::new_from_wkt("LINESTRING(1 3 4, 5 6 7)")?;
    /// assert_eq!(line.get_x_min()?, 1.);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_7_0")]
    fn get_x_min(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut value = 0.0;
            errcheck!(GEOSGeom_getXMin_r(ctx.as_raw(), self.as_raw(), &mut value))?;
            Ok(value)
        })
    }

    /// Returns the biggest Y of the geometry.
    ///
    /// Available using the `v3_7_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let line = Geometry::new_from_wkt("LINESTRING(1 3 4, 5 6 7)")?;
    /// assert_eq!(line.get_y_max()?, 6.);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_7_0")]
    fn get_y_max(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut value = 0.0;
            errcheck!(GEOSGeom_getYMax_r(ctx.as_raw(), self.as_raw(), &mut value))?;
            Ok(value)
        })
    }

    /// Returns the smallest Y of the geometry.
    ///
    /// Available using the `v3_7_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let line = Geometry::new_from_wkt("LINESTRING(1 3 4, 5 6 7)")?;
    /// assert_eq!(line.get_y_min()?, 3.);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_7_0")]
    fn get_y_min(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut value = 0.0;
            errcheck!(GEOSGeom_getYMin_r(ctx.as_raw(), self.as_raw(), &mut value))?;
            Ok(value)
        })
    }

    /// Returns the smallest distance by which a vertex of `self` could be moved to produce an
    /// invalid geometry.
    ///
    /// Available using the `v3_6_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("LINESTRING(1 3 4, 5 6 7)")?;
    /// assert_eq!(
    ///     geom.minimum_clearance().map(|x| format!("{:.8}", x))?,
    ///     "5.00000000"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_6_0")]
    fn minimum_clearance(&self) -> GResult<f64> {
        with_context(|ctx| unsafe {
            let mut value = 0.0;
            predicate!(GEOSMinimumClearance_r(
                ctx.as_raw(),
                self.as_raw(),
                &mut value
            ))?;
            Ok(value)
        })
    }

    /// Returns the two-point `LineString` spanning of `self`'s minimum clearance.
    ///
    /// Available using the `v3_6_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POLYGON ((0 0, 1 0, 1 1, 0.5 3.2e-4, 0 0))")?;
    /// let line = geom.minimum_clearance_line()?;
    /// assert_eq!(line.to_wkt()?, "LINESTRING (0.5 0.00032, 0.5 0)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_6_0")]
    fn minimum_clearance_line(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSMinimumClearanceLine_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the minimum rotated rectangle inside of `self`.
    ///
    /// Available using the `v3_6_0` feature.
    #[cfg(feature = "v3_6_0")]
    fn minimum_rotated_rectangle(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSMinimumRotatedRectangle_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the minimum width inside of `self`.
    ///
    /// Available using the `v3_6_0` feature.
    #[cfg(feature = "v3_6_0")]
    fn minimum_width(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSMinimumWidth_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns a [delaunay triangulation](https://en.wikipedia.org/wiki/Delaunay_triangulation)
    /// around the vertices of `self`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POLYGON((175 150, 20 40, 50 60, 125 100, 175 150))")?;
    /// let geom2 = Geometry::new_from_wkt("POINT(110 170)")?;
    /// let geom2 = geom2.buffer(20., 8)?;
    ///
    /// let geom = geom1.union(&geom2)?;
    ///
    /// let final_geom = geom.delaunay_triangulation(0.001, false)?;
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn delaunay_triangulation(&self, tolerance: f64, only_edges: bool) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSDelaunayTriangulation_r(
                ctx.as_raw(),
                self.as_raw(),
                tolerance,
                only_edges.into(),
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    fn interpolate(&self, d: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSInterpolate_r(ctx.as_raw(), self.as_raw(), d))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    fn interpolate_normalized(&self, d: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSInterpolateNormalized_r(ctx.as_raw(), self.as_raw(), d))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    fn project<G: Geom>(&self, p: &G) -> GResult<f64> {
        with_context(|ctx| unsafe {
            errcheck!(-1.0, GEOSProject_r(ctx.as_raw(), self.as_raw(), p.as_raw()))
        })
    }

    fn project_normalized<G: Geom>(&self, p: &G) -> GResult<f64> {
        with_context(|ctx| unsafe {
            errcheck!(
                -1.0,
                GEOSProjectNormalized_r(ctx.as_raw(), self.as_raw(), p.as_raw())
            )
        })
    }

    fn node(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSNode_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    ///  Return an offset line at a given distance and side from an input line. All points of the
    /// returned geometries are not further than the given distance from the input geometry.
    ///
    /// # Parameters:
    ///
    /// - `width`:
    ///   * If `width` is positive, the offset will be at the left side of the input line and retain
    ///     the same direction.
    ///   * If `width` is negative, it'll be at the right side and in the opposite direction.
    /// - `quadrant_segments`:
    ///   * If `quadrant_segments` is >= 1, joins are round, and `quadrant_segments` indicates the
    ///     number of segments to use to approximate a quarter-circle.
    ///   * If `quadrant_segments` == 0, joins are bevelled (flat).
    ///   * If `quadrant_segments` < 0, joins are mitred, and the value of `quadrant_segments`
    ///     indicates the mitre ration limit as `mitre_limit = |quadrant_segments|`
    /// - `mitre_limit`:
    ///   The mitre ratio is the ratio of the distance from the corner to the end of the mitred offset
    ///   corner. When two line segments meet at a sharp angle, a miter join will extend far beyond
    ///   the original geometry (and in the extreme case will be infinitely far). To prevent
    ///   unreasonable geometry, the mitre limit allows controlling the maximum length of the join
    ///   corner. Corners with a ratio which exceed the limit will be beveled.
    fn offset_curve(
        &self,
        width: f64,
        quadrant_segments: i32,
        join_style: JoinStyle,
        mitre_limit: f64,
    ) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSOffsetCurve_r(
                ctx.as_raw(),
                self.as_raw(),
                width,
                quadrant_segments,
                join_style.into(),
                mitre_limit,
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    fn point_on_surface(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSPointOnSurface_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns, in the tuple elements order:
    ///
    /// 1. The polygonized geometry.
    /// 2. The cuts geometries collection.
    /// 3. The dangles geometries collection.
    /// 4. The invalid geometries collection.
    #[allow(clippy::type_complexity)]
    fn polygonize_full(
        &self,
    ) -> GResult<(
        Geometry,
        Option<Geometry>,
        Option<Geometry>,
        Option<Geometry>,
    )> {
        let mut cuts: *mut GEOSGeometry = ::std::ptr::null_mut();
        let mut dangles: *mut GEOSGeometry = ::std::ptr::null_mut();
        let mut invalids: *mut GEOSGeometry = ::std::ptr::null_mut();

        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSPolygonize_full_r(
                ctx.as_raw(),
                self.as_raw(),
                &mut cuts,
                &mut dangles,
                &mut invalids,
            ))?;
            let geom = Geometry::new_from_raw(ptr);

            let cuts = NonNull::new(cuts).map(Geometry::new_from_raw);
            let dangles = NonNull::new(dangles).map(Geometry::new_from_raw);
            let invalids = NonNull::new(invalids).map(Geometry::new_from_raw);
            Ok((geom, cuts, dangles, invalids))
        })
    }

    fn shared_paths<G: Geom>(&self, other: &G) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSSharedPaths_r(
                ctx.as_raw(),
                self.as_raw(),
                other.as_raw()
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Converts a [`Geometry`] to the HEX format. For more control over the generated output,
    /// use the [`WKBWriter`](crate::WKBWriter) type.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// let hex_buf = point_geom.to_hex()?;
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn to_hex(&self) -> GResult<Vec<u8>> {
        let mut size = 0;
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeomToHEX_buf_r(ctx.as_raw(), self.as_raw(), &mut size))?;
            Ok(managed_vec(ptr, size, ctx))
        })
    }

    /// Converts a [`Geometry`] to the WKB format. For more control over the generated output,
    /// use the [`WKBWriter`](crate::WKBWriter) type.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// let wkb_buf = point_geom.to_wkb()?;
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn to_wkb(&self) -> GResult<Vec<u8>> {
        let mut size = 0;
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeomToWKB_buf_r(ctx.as_raw(), self.as_raw(), &mut size))?;
            Ok(managed_vec(ptr, size, ctx))
        })
    }

    /// Converts a [`Geometry`] to the `GeoJSON` format. For more control over the generated output,
    /// use the [`GeoJSONWriter`](crate::GeoJSONWriter) type.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// assert_eq!(
    ///     point_geom.to_geojson()?,
    ///     r#"{"type":"Point","coordinates":[2.5,2.5]}"#,
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_10_0")]
    fn to_geojson(&self) -> GResult<String> {
        GeoJSONWriter::new()?.write(self)
    }

    #[cfg(feature = "v3_10_0")]
    fn to_geojson_formatted(&self, indent: i32) -> GResult<String> {
        GeoJSONWriter::new()?.write_formatted(self, indent)
    }

    /// Creates a new [`PreparedGeometry`] from the current `Geometry`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// let prepared_geom = point_geom.to_prepared_geom()?;
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn to_prepared_geom(&self) -> GResult<PreparedGeometry<'_>> {
        PreparedGeometry::new(self)
    }

    fn clone(&self) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_clone_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Returns the 1-based nth geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("MULTIPOINT(1 1, 2 2, 3 3, 4 4)")?;
    /// let point_nb3 = geom.get_geometry_n(2)?;
    /// assert_eq!(point_nb3.to_wkt()?, "POINT (3 3)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_geometry_n(&self, n: usize) -> GResult<ConstGeometry<'_>> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGetGeometryN_r(ctx.as_raw(), self.as_raw(), n as _))?;
            Ok(ConstGeometry::new_from_raw(ptr))
        })
    }

    /// Returns the nth interior ring.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt(
    ///     "POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))",
    /// )?;
    /// let interior = geom.get_interior_ring_n(0)?;
    /// assert_eq!(interior.to_wkt()?, "LINEARRING (1 1, 2 1, 2 5, 1 5, 1 1)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_interior_ring_n(&self, n: usize) -> GResult<ConstGeometry<'_>> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGetInteriorRingN_r(ctx.as_raw(), self.as_raw(), n as _))?;
            Ok(ConstGeometry::new_from_raw(ptr))
        })
    }

    /// Returns the exterior ring.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom =
    ///     Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1))")?;
    ///
    /// let exterior = point_geom.get_exterior_ring()?;
    /// assert_eq!(exterior.to_wkt()?, "LINEARRING (0 0, 10 0, 10 6, 0 6, 0 0)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    fn get_exterior_ring(&self) -> GResult<ConstGeometry<'_>> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGetExteriorRing_r(ctx.as_raw(), self.as_raw()))?;
            Ok(ConstGeometry::new_from_raw(ptr))
        })
    }

    /// Apply XY coordinate transform callback to all coordinates in a copy of input geometry.
    /// If the callback returns an error, the function will return an Err.
    /// Z and M values, if present, are not modified by this function.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt("POINT (1.5 2.5)")?;
    /// let transformed = geom.transform_xy(|x, y| Ok::<_, geos::Error>((x + 1.0, y + 2.0)))?;
    /// assert_eq!(transformed.to_wkt()?, "POINT (2.5 4.5)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_11_0")]
    fn transform_xy<F: Fn(f64, f64) -> Result<(f64, f64), E>, E: From<Error>>(
        &self,
        on_transform_point: F,
    ) -> Result<Geometry, E> {
        let mut trampoline = Trampoline::new(on_transform_point);

        with_context(|ctx| unsafe {
            let ptr = GEOSGeom_transformXY_r(
                ctx.as_raw(),
                self.as_raw(),
                trampoline.get_xy_callback(),
                trampoline.as_mut_void(),
            );
            if let Some(ptr) = NonNull::new(ptr) {
                Ok(Geometry::new_from_raw(ptr))
            } else if let Some(err) = trampoline.err {
                Err(err)
            } else {
                Err(Error::GeosError(("GEOSGeom_transformXY_r", ctx.get_last_error())).into())
            }
        })
    }

    #[cfg(feature = "v3_14_0")]
    fn transform_xyz<F: Fn(f64, f64, f64) -> Result<(f64, f64, f64), E>, E: From<Error>>(
        &self,
        on_transform_point: F,
    ) -> Result<Geometry, E> {
        let mut trampoline = Trampoline::new(on_transform_point);

        with_context(|ctx| unsafe {
            let ptr = GEOSGeom_transformXYZ_r(
                ctx.as_raw(),
                self.as_raw(),
                trampoline.get_xyz_callback(),
                trampoline.as_mut_void(),
            );
            if let Some(ptr) = NonNull::new(ptr) {
                Ok(Geometry::new_from_raw(ptr))
            } else if let Some(err) = trampoline.err {
                Err(err)
            } else {
                Err(Error::GeosError(("GEOSGeom_transformXY_r", ctx.get_last_error())).into())
            }
        })
    }

    fn clip_by_rect(&self, xmin: f64, ymin: f64, xmax: f64, ymax: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSClipByRect_r(
                ctx.as_raw(),
                self.as_raw(),
                xmin,
                ymin,
                xmax,
                ymax
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_10_0")]
    fn densify(&self, tolerance: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSDensify_r(ctx.as_raw(), self.as_raw(), tolerance))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_11_0")]
    fn remove_repeated_points(&self, tolerance: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSRemoveRepeatedPoints_r(
                ctx.as_raw(),
                self.as_raw(),
                tolerance
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_11_0")]
    fn concave_hull(&self, ratio: f64, allow_holes: bool) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSConcaveHull_r(
                ctx.as_raw(),
                self.as_raw(),
                ratio,
                allow_holes.into()
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_11_0")]
    fn get_extent(&self) -> GResult<Vec<f64>> {
        with_context(|ctx| unsafe {
            let mut xmin: f64 = 0.;
            let mut ymin: f64 = 0.;
            let mut xmax: f64 = 0.;
            let mut ymax: f64 = 0.;
            errcheck!(GEOSGeom_getExtent_r(
                ctx.as_raw(),
                self.as_raw(),
                &mut xmin,
                &mut ymin,
                &mut xmax,
                &mut ymax
            ))?;
            Ok(vec![xmin, ymin, xmax, ymax])
        })
    }

    #[cfg(feature = "v3_12_0")]
    fn line_substring(&self, start_fraction: f64, end_fraction: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSLineSubstring_r(
                ctx.as_raw(),
                self.as_raw(),
                start_fraction,
                end_fraction as _,
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }

    /// Compute the Maximum Inscribed Circle (MIC) of a polygonal geometry,
    /// which is the largest circle that fits inside the geometry.
    /// This is useful to find the most "central" point of a geometry (also called
    /// the "pole of inaccessibility"), and it is sometimes used
    /// in cartography for label placement.
    /// The algorithm is stopped when the search area is smaller than the given tolerance.
    ///
    /// Returns a two-point linestring, where the first point is the center of the
    /// inscribed circle and the second point is on the boundary of the inscribed circle.
    ///
    /// # Example
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let poly = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))")?;
    /// let mic = poly.maximum_inscribed_circle(1.)?;
    ///
    /// let center = mic.get_start_point()?;
    /// assert_eq!(
    ///     center.to_wkt_precision(0)?,
    ///     "POINT (5 5)"
    /// );
    ///
    /// let radius = mic.length()?;
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_9_0")]
    fn maximum_inscribed_circle(&self, tolerance: f64) -> GResult<Geometry> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSMaximumInscribedCircle_r(
                ctx.as_raw(),
                self.as_raw(),
                tolerance
            ))?;
            Ok(Geometry::new_from_raw(ptr))
        })
    }
}

/// Trampoline function helper function to get the trampoline function from the closure.
#[cfg(feature = "v3_11_0")]
struct Trampoline<F, E: From<Error>> {
    closure: F,
    err: Option<E>,
}

#[cfg(feature = "v3_11_0")]
impl<F, E: From<Error>> Trampoline<F, E> {
    const fn new(closure: F) -> Self {
        Self { closure, err: None }
    }

    fn as_mut_void(&mut self) -> *mut libc::c_void {
        self as *mut _ as *mut _
    }
}

#[cfg(feature = "v3_11_0")]
impl<F: FnMut(f64, f64) -> Result<(f64, f64), E>, E: From<Error>> Trampoline<F, E> {
    fn get_xy_callback(&self) -> GEOSTransformXYCallback {
        unsafe extern "C" fn transform_trampoline<F, E>(
            x: *mut libc::c_double,
            y: *mut libc::c_double,
            user_data: *mut libc::c_void,
        ) -> libc::c_int
        where
            F: FnMut(f64, f64) -> Result<(f64, f64), E>,
            E: From<Error>,
        {
            let trampoline = &mut *user_data.cast::<Trampoline<F, E>>();
            let closure = &mut trampoline.closure;
            match closure(*x, *y) {
                Ok((new_x, new_y)) => {
                    *x = new_x;
                    *y = new_y;
                    1
                }
                Err(error) => {
                    trampoline.err = Some(error);
                    0
                }
            }
        }

        Some(transform_trampoline::<F, E>)
    }
}

#[cfg(feature = "v3_14_0")]
impl<F: FnMut(f64, f64, f64) -> Result<(f64, f64, f64), E>, E: From<Error>> Trampoline<F, E> {
    fn get_xyz_callback(&self) -> GEOSTransformXYZCallback {
        unsafe extern "C" fn transform_trampoline<F, E>(
            x: *mut libc::c_double,
            y: *mut libc::c_double,
            z: *mut libc::c_double,
            user_data: *mut libc::c_void,
        ) -> libc::c_int
        where
            F: FnMut(f64, f64, f64) -> Result<(f64, f64, f64), E>,
            E: From<Error>,
        {
            let trampoline = &mut *user_data.cast::<Trampoline<F, E>>();
            let closure = &mut trampoline.closure;
            match closure(*x, *y, *z) {
                Ok((new_x, new_y, new_z)) => {
                    *x = new_x;
                    *y = new_y;
                    *z = new_z;
                    1
                }
                Err(error) => {
                    trampoline.err = Some(error);
                    0
                }
            }
        }

        Some(transform_trampoline::<F, E>)
    }
}

impl ConstGeometry<'_> {
    pub(crate) const fn new_from_raw(ptr: NonNull<GEOSGeometry>) -> Self {
        ConstGeometry {
            ptr,
            phantom: PhantomData,
        }
    }
}

impl Geometry {
    pub(crate) const fn new_from_raw(ptr: NonNull<GEOSGeometry>) -> Self {
        Self { ptr }
    }

    /// Creates a `Geometry` from the WKT format.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::Geometry;
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn new_from_wkt(wkt: &str) -> GResult<Self> {
        with_context(|ctx| match CString::new(wkt) {
            Ok(c_str) => unsafe {
                let reader = nullcheck!(GEOSWKTReader_create_r(ctx.as_raw()))?.as_ptr();
                let ptr = nullcheck!(GEOSWKTReader_read_r(ctx.as_raw(), reader, c_str.as_ptr()))?;
                GEOSWKTReader_destroy_r(ctx.as_raw(), reader);
                Ok(Self::new_from_raw(ptr))
            },
            Err(e) => Err(Error::GenericError(format!(
                "Conversion to CString failed with {e}",
            ))),
        })
    }

    /// Create a new [`Geometry`] from the HEX format.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// let hex_buf = point_geom.to_hex()?;
    ///
    /// // The interesting part is here:
    /// let new_geom = Geometry::new_from_hex(hex_buf.as_ref())?;
    /// assert_eq!(point_geom.equals(&new_geom)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn new_from_hex(hex: &[u8]) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeomFromHEX_buf_r(ctx.as_raw(), hex.as_ptr(), hex.len()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Create a new [`Geometry`] from the WKB format.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let point_geom = Geometry::new_from_wkt("POINT (2.5 2.5)")?;
    /// let wkb_buf = point_geom.to_wkb()?;
    ///
    /// // The interesting part is here:
    /// let new_geom = Geometry::new_from_wkb(wkb_buf.as_ref())?;
    /// assert_eq!(point_geom.equals(&new_geom)?, true);
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn new_from_wkb(wkb: &[u8]) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeomFromWKB_buf_r(ctx.as_raw(), wkb.as_ptr(), wkb.len()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Creates a `Geometry` from the `GeoJSON` format.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::Geometry;
    ///
    /// let point_geom = Geometry::new_from_geojson(r#"{"type": "Point", "coordinates": [2.5, 2.5]}"#)?;
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_10_0")]
    pub fn new_from_geojson(wkt: &str) -> GResult<Self> {
        with_context(|ctx| match CString::new(wkt) {
            Ok(c_str) => unsafe {
                let reader = nullcheck!(GEOSGeoJSONReader_create_r(ctx.as_raw()))?;
                let ptr = nullcheck!(GEOSGeoJSONReader_readGeometry_r(
                    ctx.as_raw(),
                    reader.as_ptr(),
                    c_str.as_ptr()
                ))?;
                GEOSGeoJSONReader_destroy_r(ctx.as_raw(), reader.as_ptr());
                Ok(Self::new_from_raw(ptr))
            },
            Err(e) => Err(Error::GenericError(format!(
                "Conversion to CString failed: {e}",
            ))),
        })
    }

    /// Creates an areal geometry formed by the constituent linework of given geometry.
    ///
    /// You can find new illustrations on [postgis](https://postgis.net/docs/ST_BuildArea.html)
    /// documentation.
    ///
    /// Available using the `v3_8_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::new_from_wkt(
    ///     "GEOMETRYCOLLECTION(POLYGON((0 0, 4 0, 4 4, 0 4, 0 0)), POLYGON((1 1, 1 3, 3 3, 3 1, 1 1)))",
    /// )?;
    ///
    /// let build_area_geom = geom.build_area()?;
    /// // Square polygon with square hole.
    /// assert_eq!(
    ///     build_area_geom.to_wkt()?,
    ///     "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0), (1 1, 3 1, 3 3, 1 3, 1 1))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_8_0")]
    pub fn build_area(&self) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSBuildArea_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Description from [postgis](https://postgis.net/docs/ST_Polygonize.html):
    ///
    /// > Creates a GeometryCollection containing possible polygons formed from the constituent
    /// > linework of a set of geometries.
    ///
    /// # Example:
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt(
    ///     "POLYGON((-71.040888 42.285678,\
    ///               -71.040943 42.2856,\
    ///               -71.04096 42.285752,\
    ///               -71.040888 42.285678))",
    /// )?;
    /// let geom2 = Geometry::new_from_wkt(
    ///     "POLYGON((-71.17166 42.353675,\
    ///               -71.172026 42.354044,\
    ///               -71.17238 42.354358,\
    ///               -71.17179 42.354971,\
    ///               -71.170511 42.354855,\
    ///               -71.17112 42.354238,\
    ///               -71.17166 42.353675))",
    /// )?;
    ///
    /// let polygonized = Geometry::polygonize(&[geom1, geom2])?;
    /// assert_eq!(
    ///     polygonized.to_wkt()?,
    ///     "GEOMETRYCOLLECTION (POLYGON ((-71.040888 42.285678, \
    ///                                    -71.040943 42.2856, \
    ///                                    -71.04096 42.285752, \
    ///                                    -71.040888 42.285678)), \
    ///                          POLYGON ((-71.17166 42.353675, \
    ///                                    -71.172026 42.354044, \
    ///                                    -71.17238 42.354358, \
    ///                                    -71.17179 42.354971, \
    ///                                    -71.170511 42.354855, \
    ///                                    -71.17112 42.354238, \
    ///                                    -71.17166 42.353675)))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn polygonize<T: Borrow<Self>>(geometries: &[T]) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let geoms = geometries
                .iter()
                .map(|g| g.borrow().as_raw().cast())
                .collect::<Vec<_>>();
            let ptr = nullcheck!(GEOSPolygonize_r(
                ctx.as_raw(),
                geoms.as_ptr(),
                geoms.len() as _
            ))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    pub fn polygonizer_get_cut_edges<T: Borrow<Self>>(&self, geometries: &[T]) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let geoms = geometries
                .iter()
                .map(|g| g.borrow().as_raw().cast())
                .collect::<Vec<_>>();
            let ptr = nullcheck!(GEOSPolygonizer_getCutEdges_r(
                ctx.as_raw(),
                geoms.as_ptr(),
                geoms.len() as _
            ))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Merges `Multi Line String` geometry into a (set of) `Line String`.
    ///
    /// ### Warning
    ///
    /// If you use this function on something else than a `Multi Line String` or a
    /// `Line String`, it'll return an empty `Geometry collection`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let lines = Geometry::new_from_wkt(
    ///     "MULTILINESTRING((-29 -27,-30 -29.7,-36 -31,-45 -33), (-45 -33,-46 -32))",
    /// )?;
    /// let lines_merged = lines.line_merge()?;
    /// assert_eq!(
    ///     lines_merged.to_wkt()?,
    ///     "LINESTRING (-29 -27, -30 -29.7, -36 -31, -45 -33, -46 -32)",
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn line_merge(&self) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSLineMerge_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    #[cfg(feature = "v3_11_0")]
    pub fn line_merge_directed(&self) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSLineMergeDirected_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Reverses the order of the vertexes.
    ///
    /// Available using the `v3_7_0` feature.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let line = Geometry::new_from_wkt("LINESTRING(1 10,1 2)")?;
    /// let reversed_line = line.reverse()?;
    ///
    /// assert_eq!(reversed_line.to_wkt()?, "LINESTRING (1 2, 1 10)",);
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_7_0")]
    pub fn reverse(&self) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSReverse_r(ctx.as_raw(), self.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Returns a simplified version of the given geometry.
    pub fn simplify(&self, tolerance: f64) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSSimplify_r(ctx.as_raw(), self.as_raw(), tolerance))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Returns a simplified version of the given geometry. It will avoid creating invalid derived
    /// geometries.
    pub fn topology_preserve_simplify(&self, tolerance: f64) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSTopologyPreserveSimplify_r(
                ctx.as_raw(),
                self.as_raw(),
                tolerance
            ))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Set SRID of `self`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let mut point_geom = Geometry::new_from_wkt("POINT (2.5 2.5 4.0)")?;
    /// point_geom.set_srid(4326);
    /// assert_eq!(point_geom.get_srid()?, 4326);
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn set_srid(&mut self, srid: libc::c_int) {
        with_context(|ctx| unsafe { GEOSSetSRID_r(ctx.as_raw(), self.as_raw_mut(), srid) });
    }

    /// Normalizes `self` in its normalized/canonical form. May reorder vertices in polygon rings,
    /// rings in a polygon, elements in a multi-geometry complex.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let mut geom = Geometry::new_from_wkt(
    ///     "GEOMETRYCOLLECTION(POINT(2 3), MULTILINESTRING((0 0, 1 1),(2 2, 3 3)))",
    /// )?;
    ///
    /// geom.normalize()?;
    ///
    /// assert_eq!(
    ///     geom.to_wkt()?,
    ///     "GEOMETRYCOLLECTION (MULTILINESTRING ((2 2, 3 3), (0 0, 1 1)), POINT (2 3))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn normalize(&mut self) -> GResult<()> {
        with_context(|ctx| unsafe {
            errcheck!(-1, GEOSNormalize_r(ctx.as_raw(), self.as_raw_mut()))?;
            Ok(())
        })
    }

    /// Creates an empty polygon geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::create_empty_polygon()?;
    ///
    /// assert_eq!(geom.to_wkt()?, "POLYGON EMPTY");
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_empty_polygon() -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createEmptyPolygon_r(ctx.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Creates an empty point geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::create_empty_point()?;
    ///
    /// assert_eq!(geom.to_wkt()?, "POINT EMPTY");
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_empty_point() -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createEmptyPoint_r(ctx.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Creates an empty line string geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::create_empty_line_string()?;
    ///
    /// assert_eq!(geom.to_wkt()?, "LINESTRING EMPTY");
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_empty_line_string() -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createEmptyLineString_r(ctx.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Creates an empty circular string geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::create_empty_circular_string()?;
    ///
    /// assert_eq!(geom.to_wkt()?, "CIRCULARSTRING EMPTY");
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_13_0")]
    pub fn create_empty_circular_string() -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createEmptyCircularString_r(ctx.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Creates an empty compound curve geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::create_empty_compound_curve()?;
    ///
    /// assert_eq!(geom.to_wkt()?, "COMPOUNDCURVE EMPTY");
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_13_0")]
    pub fn create_empty_compound_curve() -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createEmptyCompoundCurve_r(ctx.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Creates an empty curve polygon geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::create_empty_curve_polygon()?;
    ///
    /// assert_eq!(geom.to_wkt()?, "CURVEPOLYGON EMPTY");
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_13_0")]
    pub fn create_empty_curve_polygon() -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createEmptyCurvePolygon_r(ctx.as_raw()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Creates an empty collection.
    ///
    /// The `type_` must be one of:
    ///
    /// * [`GeometryTypes::GeometryCollection`]
    /// * [`GeometryTypes::MultiPoint`]
    /// * [`GeometryTypes::MultiLineString`]
    /// * [`GeometryTypes::MultiPolygon`]
    /// * [`GeometryTypes::MultiCurve`]
    /// * [`GeometryTypes::MultiSurface`]
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry, GeometryTypes};
    ///
    /// let geom = Geometry::create_empty_collection(GeometryTypes::MultiPolygon)?;
    ///
    /// assert_eq!(geom.to_wkt()?, "MULTIPOLYGON EMPTY");
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_empty_collection(type_: GeometryTypes) -> GResult<Self> {
        if !type_.is_collection() {
            return Err(Error::GenericError(format!(
                "invalid collection type: {type_:?}"
            )));
        }

        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createEmptyCollection_r(ctx.as_raw(), type_.into()))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Creates a polygon formed by the given shell and array of holes.
    ///
    /// ### Note
    ///
    /// `exterior` must be a `LinearRing`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom =
    ///     Geometry::new_from_wkt("LINEARRING(75.16 29.53, 78.2 29.2, 77.7 29.5, 75.16 29.53)")?;
    /// let polygon_geom = Geometry::create_polygon(geom, vec![])?;
    ///
    /// assert_eq!(
    ///     polygon_geom.to_wkt()?,
    ///     "POLYGON ((75.16 29.53, 78.2 29.2, 77.7 29.5, 75.16 29.53))",
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_polygon(mut exterior: Self, mut interiors: Vec<Self>) -> GResult<Self> {
        let nb_interiors = interiors.len();
        let polygon = with_context(|ctx| unsafe {
            let mut geoms: Vec<*mut GEOSGeometry> =
                interiors.iter_mut().map(AsRawMut::as_raw_mut).collect();
            let ptr = nullcheck!(GEOSGeom_createPolygon_r(
                ctx.as_raw(),
                exterior.as_raw_mut(),
                geoms.as_mut_ptr().cast(),
                nb_interiors as _,
            ))?;
            Ok(Self::new_from_raw(ptr))
        });

        // We transferred the ownership of the ptr to the new Geometry,
        // so the old ones need to forget their c ptr to avoid double free.
        std::mem::forget(exterior);
        for interior in interiors {
            std::mem::forget(interior);
        }

        polygon
    }

    /// Create a geometry collection.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0))")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (3.0 4.0)")?;
    ///
    /// let geom = Geometry::create_geometry_collection(vec![geom1, geom2])?;
    ///
    /// assert_eq!(
    ///     geom.to_wkt()?,
    ///     "GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 6, 0 6, 0 0)), POINT (3 4))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_geometry_collection(geoms: Vec<Self>) -> GResult<Self> {
        create_multi_geom(geoms, GeometryTypes::GeometryCollection)
    }

    /// Create a multi polygon geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0))")?;
    /// let geom2 = Geometry::new_from_wkt("POLYGON((3 3, 10 3, 10 6, 3 6, 3 3))")?;
    ///
    /// let geom = Geometry::create_multipolygon(vec![geom1, geom2])?;
    ///
    /// assert_eq!(
    ///     geom.to_wkt()?,
    ///     "MULTIPOLYGON (((0 0, 10 0, 10 6, 0 6, 0 0)), \
    ///                           ((3 3, 10 3, 10 6, 3 6, 3 3)))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_multipolygon(polygons: Vec<Self>) -> GResult<Self> {
        if !check_same_geometry_type(&polygons, GeometryTypes::Polygon) {
            return Err(Error::ImpossibleOperation(
                "all the provided geometry have to be of type Polygon".to_owned(),
            ));
        }
        create_multi_geom(polygons, GeometryTypes::MultiPolygon)
    }

    /// Create a multiline string geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("LINESTRING (1.0 2.0, 3.0 4.0)")?;
    /// let geom2 = Geometry::new_from_wkt("LINESTRING (5.0 6.0, 7.0 8.0)")?;
    ///
    /// let geom = Geometry::create_multiline_string(vec![geom1, geom2])?;
    ///
    /// assert_eq!(geom.to_wkt()?, "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))");
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_multiline_string(linestrings: Vec<Self>) -> GResult<Self> {
        if !check_same_geometry_type(&linestrings, GeometryTypes::LineString) {
            return Err(Error::ImpossibleOperation(
                "all the provided geometry have to be of type LineString".to_owned(),
            ));
        }
        create_multi_geom(linestrings, GeometryTypes::MultiLineString)
    }

    /// Creates a multi point geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry, GeometryTypes};
    ///
    /// let geom1 = Geometry::new_from_wkt("POINT (1 2)")?;
    /// let geom2 = Geometry::new_from_wkt("POINT (3 4)")?;
    ///
    /// let geom = Geometry::create_multipoint(vec![geom1, geom2])?;
    ///
    /// assert_eq!(geom.geometry_type()?, GeometryTypes::MultiPoint);
    /// assert_eq!(geom.get_num_geometries()?, 2);
    /// assert_eq!(geom.get_geometry_n(0)?.to_wkt()?, "POINT (1 2)");
    /// assert_eq!(geom.get_geometry_n(1)?.to_wkt()?, "POINT (3 4)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_multipoint(points: Vec<Self>) -> GResult<Self> {
        if !check_same_geometry_type(&points, GeometryTypes::Point) {
            return Err(Error::ImpossibleOperation(
                "all the provided geometry have to be of type Point".to_owned(),
            ));
        }
        create_multi_geom(points, GeometryTypes::MultiPoint)
    }

    /// Create a multicurve geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("LINESTRING (1.0 2.0, 3.0 4.0)")?;
    /// let geom2 = Geometry::new_from_wkt("CIRCULARSTRING (5.0 6.0, 7.0 8.0, 9.0 10.0)")?;
    ///
    /// let geom = Geometry::create_multicurve(vec![geom1, geom2])?;
    ///
    /// assert_eq!(
    ///     geom.to_wkt()?,
    ///     "MULTICURVE ((1 2, 3 4), CIRCULARSTRING (5 6, 7 8, 9 10))"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_13_0")]
    pub fn create_multicurve(curves: Vec<Self>) -> GResult<Self> {
        if !curves
            .iter()
            .all(|g| g.geometry_type().is_ok_and(GeometryTypes::is_curve))
        {
            return Err(Error::ImpossibleOperation(
                "all the provided geometry have to be of type LineString or CircularString"
                    .to_owned(),
            ));
        }
        create_multi_geom(curves, GeometryTypes::MultiCurve)
    }

    /// Create a multisurface geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom1 = Geometry::new_from_wkt("POLYGON((0 0, 1 1, 1 2, 1 1, 0 0))")?;
    /// let geom2 = Geometry::new_from_wkt("CURVEPOLYGON(CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3))")?;
    ///
    /// let geom = Geometry::create_multisurface(vec![geom1, geom2])?;
    ///
    /// assert_eq!(geom.to_wkt()?,
    ///            "MULTISURFACE (((0 0, 1 1, 1 2, 1 1, 0 0)), CURVEPOLYGON (CIRCULARSTRING (1 3, 3 5, 4 7, 7 3, 1 3)))");
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_13_0")]
    pub fn create_multisurface(surfaces: Vec<Self>) -> GResult<Self> {
        if !surfaces
            .iter()
            .all(|g| g.geometry_type().is_ok_and(GeometryTypes::is_surface))
        {
            return Err(Error::ImpossibleOperation(
                "all the provided geometry have to be of type Polygon or CurvePolygon".to_owned(),
            ));
        }
        create_multi_geom(surfaces, GeometryTypes::MultiSurface)
    }

    /// Creates a point geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq, Geom, Geometry};
    ///
    /// let coords = CoordSeq::new_from_vec(&[&[1., 2.]])?;
    ///
    /// let geom = Geometry::create_point(coords)?;
    ///
    /// assert_eq!(geom.to_wkt()?, "POINT (1 2)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_point(mut s: CoordSeq) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createPoint_r(ctx.as_raw(), s.as_raw_mut()));
            std::mem::forget(s);
            Ok(Self::new_from_raw(ptr?))
        })
    }

    /// Creates a line string geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq, Geom, Geometry};
    ///
    /// let coords = CoordSeq::new_from_vec(&[&[1., 2.], &[3., 4.]])?;
    ///
    /// let geom = Geometry::create_line_string(coords)?;
    ///
    /// assert_eq!(geom.to_wkt()?, "LINESTRING (1 2, 3 4)");
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_line_string(mut s: CoordSeq) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createLineString_r(ctx.as_raw(), s.as_raw_mut()));
            std::mem::forget(s);
            Ok(Self::new_from_raw(ptr?))
        })
    }

    /// Creates a linear ring geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq, Geom, Geometry};
    ///
    /// let coords =
    ///     CoordSeq::new_from_vec(&[&[75.2, 29.6], &[77.2, 29.2], &[77.7, 29.5], &[75.2, 29.6]])?;
    ///
    /// let geom = Geometry::create_linear_ring(coords)?;
    ///
    /// assert_eq!(
    ///     geom.to_wkt()?,
    ///     "LINEARRING (75.2 29.6, 77.2 29.2, 77.7 29.5, 75.2 29.6)"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    pub fn create_linear_ring(mut s: CoordSeq) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createLinearRing_r(ctx.as_raw(), s.as_raw_mut()));
            std::mem::forget(s);
            Ok(Self::new_from_raw(ptr?))
        })
    }

    /// Creates a rectangular polygon geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{Geom, Geometry};
    ///
    /// let geom = Geometry::create_rectangle(0., 0., 1., 1.)?;
    ///
    /// assert_eq!(geom.to_wkt()?, "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))");
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_11_0")]
    pub fn create_rectangle(xmin: f64, ymin: f64, xmax: f64, ymax: f64) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createRectangle_r(
                ctx.as_raw(),
                xmin,
                ymin,
                xmax,
                ymax
            ))?;
            Ok(Self::new_from_raw(ptr))
        })
    }

    /// Creates a circular string geometry.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{CoordDimensions, CoordSeq, Geom, Geometry};
    ///
    /// let coords = CoordSeq::new_from_vec(&[
    ///     &[75.15, 29.53],
    ///     &[77.2, 29.1],
    ///     &[77.6, 29.5],
    ///     &[75.15, 29.53],
    /// ])?;
    ///
    /// let geom = Geometry::create_circular_string(coords)?;
    ///
    /// assert_eq!(
    ///     geom.to_wkt_precision(1)?,
    ///     "CIRCULARSTRING (75.2 29.5, 77.2 29.1, 77.6 29.5, 75.2 29.5)"
    /// );
    /// # Ok::<(), geos::Error>(())
    /// ```
    #[cfg(feature = "v3_13_0")]
    pub fn create_circular_string(mut s: CoordSeq) -> GResult<Self> {
        with_context(|ctx| unsafe {
            let ptr = nullcheck!(GEOSGeom_createCircularString_r(
                ctx.as_raw(),
                s.as_raw_mut()
            ));
            std::mem::forget(s);
            Ok(Self::new_from_raw(ptr?))
        })
    }
}

impl<G: Geom> PartialEq<G> for ConstGeometry<'_> {
    fn eq(&self, other: &G) -> bool {
        self.equals(other).unwrap_or(false)
    }
}

impl<G: Geom> PartialEq<G> for Geometry {
    fn eq(&self, other: &G) -> bool {
        self.equals(other).unwrap_or(false)
    }
}

impl Clone for Geometry {
    fn clone(&self) -> Self {
        Geom::clone(self).expect("cloning failed")
    }
}

impl Drop for Geometry {
    fn drop(&mut self) {
        with_context(|ctx| unsafe { GEOSGeom_destroy_r(ctx.as_raw(), self.as_raw_mut()) });
    }
}

as_raw_mut_impl!(Geometry, GEOSGeometry);
as_raw_impl!(ConstGeometry<'_>, GEOSGeometry);

#[cfg(test)]
mod test {
    #[cfg(feature = "v3_11_0")]
    use super::*;

    #[test]
    #[cfg(feature = "v3_11_0")]
    fn transform_point_geometry() {
        let geom = Geometry::new_from_wkt("POINT (1.5 2.5)").unwrap();
        let transformed = geom
            .transform_xy(|x, y| {
                assert_eq!(x, 1.5);
                assert_eq!(y, 2.5);

                Ok::<_, Error>((3.5, 4.5))
            })
            .unwrap();

        let expected_geom = Geometry::new_from_wkt("POINT (3.5 4.5)").unwrap();
        assert_eq!(expected_geom.equals(&transformed).unwrap(), true);
    }

    #[test]
    #[cfg(feature = "v3_11_0")]
    fn transform_point_geometry_closure_error() {
        let geom = Geometry::new_from_wkt("POINT (1.5 2.5)").unwrap();
        match geom.transform_xy(|_x, _y| Err(Error::ImpossibleOperation("foo".into()))) {
            Ok(_) => panic!("transform_xy should have failed"),
            Err(e) => assert_eq!(e, Error::ImpossibleOperation("foo".into())),
        };
    }

    #[test]
    #[cfg(feature = "v3_11_0")]
    fn transform_polygon_geometry() {
        let geom = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0))").unwrap();
        let transformed = geom
            .transform_xy(|x, y| Ok::<_, crate::Error>((x + 1.0, y + 2.0)))
            .unwrap();

        let expected_geom = Geometry::new_from_wkt("POLYGON((1 2, 11 2, 11 8, 1 8, 1 2))").unwrap();
        assert_eq!(expected_geom.equals(&transformed).unwrap(), true);
    }
}
