/***************************************************************************
                        qgsgeos.h
  -------------------------------------------------------------------
Date                 : 22 Sept 2014
Copyright            : (C) 2014 by Marco Hugentobler
email                : marco.hugentobler at sourcepole dot com
 ***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef QGSGEOS_H
#define QGSGEOS_H

#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgsgeometryengine.h"
#include "qgsgeometry.h"
#include "qgsconfig.h"
#include <geos_c.h>

class QgsLineString;
class QgsPolygon;
class QgsGeometry;
class QgsGeometryCollection;

#if !defined(USE_THREAD_LOCAL) || defined(Q_OS_WIN)
#include <QThreadStorage>
#endif

#ifndef SIP_RUN
/**
   * \class QgsGeosContext
   * \ingroup core
   * \brief Used to create and store a proj context object, correctly freeing the context upon destruction.
   * \note Not available in Python bindings
   * \since QGIS 3.38
   */
class CORE_EXPORT QgsGeosContext
{
  public:

    QgsGeosContext();
    ~QgsGeosContext();

    /**
     * Returns a thread local instance of a GEOS context, safe for use in the current thread.
     */
    static GEOSContextHandle_t get();

  private:
    GEOSContextHandle_t mContext = nullptr;

    /**
     * Thread local GEOS context storage. A new GEOS context will be created
     * for every thread.
     */

#if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
    static thread_local QgsGeosContext sGeosContext;
#else
    static QThreadStorage< QgsGeosContext * > sGeosContext;
#endif
};

/**
 * Contains geos related utilities and functions.
 * \note not available in Python bindings.
 */
namespace geos
{

  /**
   * Destroys the GEOS geometry \a geom, using the static QGIS
   * geos context.
   */
  struct GeosDeleter
  {

    /**
     * Destroys the GEOS geometry \a geom, using the static QGIS
     * geos context.
     */
    void CORE_EXPORT operator()( GEOSGeometry *geom ) const;

    /**
     * Destroys the GEOS prepared geometry \a geom, using the static QGIS
     * geos context.
     */
    void CORE_EXPORT operator()( const GEOSPreparedGeometry *geom ) const;

    /**
     * Destroys the GEOS buffer params \a params, using the static QGIS
     * geos context.
     */
    void CORE_EXPORT operator()( GEOSBufferParams *params ) const;

    /**
     * Destroys the GEOS coordinate sequence \a sequence, using the static QGIS
     * geos context.
     */
    void CORE_EXPORT operator()( GEOSCoordSequence *sequence ) const;
  };

  /**
   * Scoped GEOS pointer.
   */
  using unique_ptr = std::unique_ptr< GEOSGeometry, GeosDeleter>;

  /**
   * Scoped GEOS prepared geometry pointer.
   */
  using prepared_unique_ptr = std::unique_ptr< const GEOSPreparedGeometry, GeosDeleter>;

  /**
   * Scoped GEOS buffer params pointer.
   */
  using buffer_params_unique_ptr = std::unique_ptr< GEOSBufferParams, GeosDeleter>;

  /**
   * Scoped GEOS coordinate sequence pointer.
   */
  using coord_sequence_unique_ptr = std::unique_ptr< GEOSCoordSequence, GeosDeleter>;

}
#endif

/**
 * \ingroup core
 * \brief Does vector analysis using the GEOS library and handles import, export, and exception handling.
 *
 * \note Available in Python bindings since QGIS 3.42
 */
class CORE_EXPORT QgsGeos: public QgsGeometryEngine
{
  public:

    /**
     * GEOS geometry engine constructor
     * \param geometry The geometry
     * \param precision The precision of the grid to which to snap the geometry vertices. If 0, no snapping is performed.
     * \param flags GEOS creation flags (since QGIS 3.40)
     * \note The third parameter was prior to QGIS 3.40 a boolean which has been incorporated into the flag
     */
    QgsGeos( const QgsAbstractGeometry *geometry, double precision = 0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings );

    /**
     * Creates a new QgsGeometry object, feeding in a geometry in GEOS format.
     * This class will take ownership of the buffer.
     *
     * \note Not available in Python bindings
     */
    SIP_SKIP static QgsGeometry geometryFromGeos( GEOSGeometry *geos );

    /**
     * Creates a new QgsGeometry object, feeding in a geometry in GEOS format.
     *
     * \note Not available in Python bindings
     */
    SIP_SKIP static QgsGeometry geometryFromGeos( const geos::unique_ptr &geos );

    /**
     * Repairs the geometry using GEOS make valid routine.
     *
     * The \a method and \a keepCollapsed arguments require builds based on GEOS 3.10 or later.
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.9 or earlier when the \a method is not Qgis::MakeValidMethod::Linework or the \a keepCollapsed option is set.
     * \since QGIS 3.20
     */
    std::unique_ptr< QgsAbstractGeometry > makeValid( Qgis::MakeValidMethod method = Qgis::MakeValidMethod::Linework, bool keepCollapsed = false, QString *errorMsg = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Adds a new island polygon to a multipolygon feature
     * \param geometry geometry to add part to
     * \param newPart part to add. Ownership is NOT transferred.
     * \returns OperationResult a result code: success or reason of failure
     *
     * \note Not available in Python bindings
     */
    SIP_SKIP static Qgis::GeometryOperationResult addPart( QgsGeometry &geometry, GEOSGeometry *newPart );

    void geometryChanged() override;
    void prepareGeometry() override;

    QgsAbstractGeometry *intersection( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr, const QgsGeometryParameters &parameters = QgsGeometryParameters() ) const override;
    QgsAbstractGeometry *difference( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr, const QgsGeometryParameters &parameters = QgsGeometryParameters() ) const override;

    /**
     * Performs a fast, non-robust intersection between the geometry and
     * a \a rectangle. The returned geometry may be invalid.
     *
     * \param rectangle clipping rectangle
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns clipped geometry
     */
    std::unique_ptr< QgsAbstractGeometry > clip( const QgsRectangle &rectangle, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Subdivides the geometry. The returned geometry will be a collection containing subdivided parts
     * from the original geometry, where no part has more then the specified maximum number of nodes (\a maxNodes).
     *
     * This is useful for dividing a complex geometry into less complex parts, which are better able to be spatially
     * indexed and faster to perform further operations such as intersects on. The returned geometry parts may
     * not be valid and may contain self-intersections.
     *
     * The minimum allowed value for \a maxNodes is 8.
     *
     * Curved geometries are not supported.
     *
     * \param maxNodes Maximum nodes used
     * \param errorMsg will be set to descriptive error string if the operation fails
     * \param parameters can be used to specify parameters which control the subdivision results (since QGIS 3.28)
     *
     * \returns subdivided geometry
     */
    std::unique_ptr< QgsAbstractGeometry > subdivide( int maxNodes, QString *errorMsg SIP_OUT = nullptr, const QgsGeometryParameters &parameters = QgsGeometryParameters() ) const;

    QgsAbstractGeometry *combine( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr, const QgsGeometryParameters &parameters = QgsGeometryParameters() ) const override;
    QgsAbstractGeometry *combine( const QVector<QgsAbstractGeometry *> &geomList, QString *errorMsg, const QgsGeometryParameters &parameters = QgsGeometryParameters() ) const override;
    QgsAbstractGeometry *combine( const QVector< QgsGeometry > &, QString *errorMsg = nullptr, const QgsGeometryParameters &parameters = QgsGeometryParameters() ) const override;
    QgsAbstractGeometry *symDifference( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr, const QgsGeometryParameters &parameters = QgsGeometryParameters() ) const override;
    QgsAbstractGeometry *buffer( double distance, int segments, QString *errorMsg = nullptr ) const override;
    QgsAbstractGeometry *buffer( double distance, int segments, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg = nullptr ) const override;

    /**
     * Directly calculates the buffer for a GEOS geometry object and returns a GEOS geometry result.
     *
     * \note Not available in Python bindings
     * \since QGIS 3.42
     */
    SIP_SKIP static geos::unique_ptr buffer( const GEOSGeometry *geometry, double distance, int segments, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg = nullptr );

    QgsAbstractGeometry *simplify( double tolerance, QString *errorMsg = nullptr ) const override;
    QgsAbstractGeometry *interpolate( double distance, QString *errorMsg = nullptr ) const override;
    QgsAbstractGeometry *envelope( QString *errorMsg = nullptr ) const override;
    QgsPoint *centroid( QString *errorMsg = nullptr ) const override;
    QgsPoint *pointOnSurface( QString *errorMsg = nullptr ) const override;
    QgsAbstractGeometry *convexHull( QString *errorMsg = nullptr ) const override;
    double distance( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    bool distanceWithin( const QgsAbstractGeometry *geom, double maxdistance, QString *errorMsg = nullptr ) const override;

    /**
     * Returns TRUE if the geometry contains the point at (\a x, \a y).
     *
     * This method is more efficient than creating a temporary QgsPoint object to test for containment.
     *
     * \param x x-coordinate of point to test
     * \param y y-coordinate of point to test
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns TRUE if the geometry contains the point
     *
     * \since QGIS 3.26
     */
    bool contains( double x, double y, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns the minimum distance from the geometry to the point at (\a x, \a y).
     *
     * This method is more efficient than creating a temporary QgsPoint object to test distance.
     *
     * \param x x-coordinate of point to test
     * \param y y-coordinate of point to test
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns minimum distance
     *
     * \since QGIS 3.26
     */
    double distance( double x, double y, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns the Hausdorff distance between this geometry and another \a geometry. This is basically a measure of how similar or dissimilar 2 geometries are.
     *
     * This algorithm is an approximation to the standard Hausdorff distance. This approximation is exact or close enough for a large
     * subset of useful cases. Examples of these are:
     *
     * - computing distance between Linestrings that are roughly parallel to each other,
     *   and roughly equal in length. This occurs in matching linear networks.
     * - Testing similarity of geometries.
     *
     * If the default approximate provided by this method is insufficient, use hausdorffDistanceDensify() instead.
     *
     * \param geometry geometry to compare to
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns calculated Hausdorff distance
     *
     * \see hausdorffDistanceDensify()
     */
    double hausdorffDistance( const QgsAbstractGeometry *geometry, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns the Hausdorff distance between this geometry and another \a geometry. This is basically a measure of how similar or dissimilar 2 geometries are.
     *
     * This function accepts a \a densifyFraction argument. The function performs a segment
     * densification before computing the discrete Hausdorff distance. The \a densifyFraction parameter
     * sets the fraction by which to densify each segment. Each segment will be split into a
     * number of equal-length subsegments, whose fraction of the total length is
     * closest to the given fraction.
     *
     * This method can be used when the default approximation provided by hausdorffDistance()
     * is not sufficient. Decreasing the \a densifyFraction parameter will make the
     * distance returned approach the true Hausdorff distance for the geometries.
     *
     * \param geometry geometry to compare to
     * \param densifyFraction fraction by which to densify each segment
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns calculated densified Hausdorff distance
     *
     * \see hausdorffDistance()
     */
    double hausdorffDistanceDensify( const QgsAbstractGeometry *geometry, double densifyFraction, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns the Fréchet distance between this geometry and another \a geometry, restricted to discrete points for both geometries.
     *
     * The Fréchet distance is a measure of similarity between curves that takes into account the location and ordering of the points along the curves.
     * Therefore it is often better than the Hausdorff distance.
     *
     * This method requires a QGIS build based on GEOS 3.7 or later.
     *
     * \param geometry geometry to compare to
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns calculated Fréchet distance
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.6 or earlier.
     * \see frechetDistanceDensify()
     * \since QGIS 3.20
     */
    double frechetDistance( const QgsAbstractGeometry *geometry, QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Returns the Fréchet distance between this geometry and another \a geometry, restricted to discrete points for both geometries.
     *
     * The Fréchet distance is a measure of similarity between curves that takes into account the location and ordering of the points along the curves.
     * Therefore it is often better than the Hausdorff distance.
     *
     * This function accepts a \a densifyFraction argument. The function performs a segment
     * densification before computing the discrete Fréchet distance. The \a densifyFraction parameter
     * sets the fraction by which to densify each segment. Each segment will be split into a
     * number of equal-length subsegments, whose fraction of the total length is
     * closest to the given fraction.
     *
     * This method can be used when the default approximation provided by frechetDistance()
     * is not sufficient. Decreasing the \a densifyFraction parameter will make the
     * distance returned approach the true Fréchet distance for the geometries.
     *
     * This method requires a QGIS build based on GEOS 3.7 or later.
     *
     * \param geometry geometry to compare to
     * \param densifyFraction fraction by which to densify each segment
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns calculated densified Fréchet distance
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.6 or earlier.
     * \see frechetDistance()
     * \since QGIS 3.20
     */
    double frechetDistanceDensify( const QgsAbstractGeometry *geometry, double densifyFraction, QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    bool intersects( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    bool touches( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    bool crosses( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    bool within( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    bool overlaps( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    bool contains( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    bool disjoint( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    QString relate( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    bool relatePattern( const QgsAbstractGeometry *geom, const QString &pattern, QString *errorMsg = nullptr ) const override;
    double area( QString *errorMsg = nullptr ) const override;
    double length( QString *errorMsg = nullptr ) const override;
    bool isValid( QString *errorMsg = nullptr, bool allowSelfTouchingHoles = false, QgsGeometry *errorLoc = nullptr ) const override;
    bool isEqual( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
    bool isEmpty( QString *errorMsg = nullptr ) const override;
    bool isSimple( QString *errorMsg = nullptr ) const override;

    EngineOperationResult splitGeometry( const QgsLineString &splitLine,
                                         QVector<QgsGeometry> &newGeometries,
                                         bool topological,
                                         QgsPointSequence &topologyTestPoints,
                                         QString *errorMsg = nullptr, bool skipIntersectionCheck = false ) const override;

    /**
     * Directly calculates the offset curve for a GEOS geometry object and returns a GEOS geometry result.
     *
     * \note Not available in Python bindings
     * \since QGIS 3.42
     */
    SIP_SKIP static geos::unique_ptr offsetCurve( const GEOSGeometry *geometry, double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg = nullptr );

    QgsAbstractGeometry *offsetCurve( double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg = nullptr ) const override;

    /**
     * Returns a single sided buffer for a geometry. The buffer is only
     * applied to one side of the geometry.
     * \param distance buffer distance
     * \param segments for round joins, number of segments to approximate quarter-circle
     * \param side side of geometry to buffer (0 = left, 1 = right)
     * \param joinStyle join style for corners ( Round (1) / Miter (2) / Bevel (3) )
     * \param miterLimit limit on the miter ratio used for very sharp corners
     * \param errorMsg will be set to descriptive error string if the operation fails
     * \returns buffered geometry, or NULLPTR if buffer could not be calculated
     */
    std::unique_ptr< QgsAbstractGeometry > singleSidedBuffer( double distance, int segments, Qgis::BufferSide side,
        Qgis::JoinStyle joinStyle, double miterLimit,
        QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns the maximum inscribed circle.
     *
     * Constructs the Maximum Inscribed Circle for a  polygonal geometry, up to a specified tolerance.
     * The Maximum Inscribed Circle is determined by a point in the interior of the area
     * which has the farthest distance from the area boundary, along with a boundary point at that distance.
     * In the context of geography the center of the Maximum Inscribed Circle is known as the
     * Pole of Inaccessibility. A cartographic use case is to determine a suitable point
     * to place a map label within a polygon.
     * The radius length of the Maximum Inscribed Circle is a  measure of how "narrow" a polygon is. It is the
     * distance at which the negative buffer becomes empty.
     * The class supports polygons with holes and multipolygons.
     * The implementation uses a successive-approximation technique over a grid of square cells covering the area geometry.
     * The grid is refined using a branch-and-bound algorithm. Point containment and distance are computed in a performant
     * way by using spatial indexes.
     * Returns a two-point linestring, with one point at the center of the inscribed circle and the other
     * on the boundary of the inscribed circle.
     *
     * This method requires a QGIS build based on GEOS 3.9 or later.
     *
     * \param tolerance tolerance to determine when iteration should end
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns maximum inscribed circle geometry
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.8 or earlier.
     *
     * \since QGIS 3.20
     */
    std::unique_ptr< QgsAbstractGeometry > maximumInscribedCircle( double tolerance, QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Constructs the Largest Empty Circle for a set of obstacle geometries, up to a
     * specified tolerance.
     *
     * The Largest Empty Circle is the largest circle which has its center in the convex hull of the
     * obstacles (the boundary), and whose interior does not intersect with any obstacle.
     * The circle center is the point in the interior of the boundary which has the farthest distance from
     * the obstacles (up to tolerance). The circle is determined by the center point and a point lying on an
     * obstacle indicating the circle radius.
     * The implementation uses a successive-approximation technique over a grid of square cells covering the obstacles and boundary.
     * The grid is refined using a branch-and-bound algorithm.  Point containment and distance are computed in a performant
     * way by using spatial indexes.
     * Returns a two-point linestring, with one point at the center of the inscribed circle and the other
     * on the boundary of the inscribed circle.
     *
     * This method requires a QGIS build based on GEOS 3.9 or later.
     *
     * \param tolerance tolerance to determine when iteration should end
     * \param boundary optional set of boundary obstacles
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns largest empty circle geometry
     *
     * \warning the \a tolerance value must be a value greater than 0, or the algorithm may never converge on a solution
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.8 or earlier.
     *
     * \since QGIS 3.20
     */
    std::unique_ptr< QgsAbstractGeometry > largestEmptyCircle( double tolerance, const QgsAbstractGeometry *boundary = nullptr, QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Returns a linestring geometry which represents the minimum diameter of the geometry.
     *
     * The minimum diameter is defined to be the width of the smallest band that
     * contains the geometry, where a band is a strip of the plane defined
     * by two parallel lines. This can be thought of as the smallest hole that the geometry
     * can be moved through, with a single rotation.
     *
     * This method requires a QGIS build based on GEOS 3.6 or later.
     *
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns linestring representing the minimum diameter of the geometry
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.5 or earlier.
     *
     * \since QGIS 3.20
     */
    std::unique_ptr< QgsAbstractGeometry > minimumWidth( QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Computes the minimum clearance of a geometry.
     *
     * The minimum clearance is the smallest amount by which
     * a vertex could be moved to produce an invalid polygon, a non-simple linestring, or a multipoint with
     * repeated points.  If a geometry has a minimum clearance of 'eps', it can be said that:
     *
     * - No two distinct vertices in the geometry are separated by less than 'eps'
     * - No vertex is closer than 'eps' to a line segment of which it is not an endpoint.
     *
     * If the minimum clearance cannot be defined for a geometry (such as with a single point, or a multipoint
     * whose points are identical) a value of infinity will be returned.
     *
     * If an error occurs while calculating the clearance NaN will be returned.
     *
     * This method requires a QGIS build based on GEOS 3.6 or later.
     *
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns calculated minimum clearance of the geometry
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.5 or earlier.
     *
     * \since QGIS 3.20
     */
    double minimumClearance( QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Returns a LineString whose endpoints define the minimum clearance of a geometry.
     *
     * If the geometry has no minimum clearance, an empty LineString will be returned.
     *
     * This method requires a QGIS build based on GEOS 3.6 or later.
     *
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns linestring geometry representing the minimum clearance of the geometry
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.5 or earlier.
     *
     * \since QGIS 3.20
     */
    std::unique_ptr< QgsAbstractGeometry > minimumClearanceLine( QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Returns a (Multi)LineString representing the fully noded version of a collection of linestrings.
     *
     * The noding preserves all of the input nodes, and introduces the least possible number of new nodes.
     * The resulting linework is dissolved (duplicate lines are removed).
     *
     * The input geometry type should be a (Multi)LineString.
     *
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns (multi)linestring geometry representing the fully noded version of the collection of linestrings
     *
     * \since QGIS 3.20
     */
    std::unique_ptr< QgsAbstractGeometry > node( QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Find paths shared between the two given lineal geometries (this and \a other).
     *
     * Returns a GeometryCollection having two elements:
     *
     * - first element is a MultiLineString containing shared paths
     *   having the same direction on both inputs
     * - second element is a MultiLineString containing shared paths
     *   having the opposite direction on the two inputs
     *
     * \param other geometry to compare against
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns shared paths, or NULLPTR on exception
     *
     * \since QGIS 3.20
     */
    std::unique_ptr< QgsAbstractGeometry > sharedPaths( const QgsAbstractGeometry *other, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Reshapes the geometry using a line
     * \param reshapeWithLine the line used to reshape lines or polygons
     * \param errorCode if specified, provides result of operation (success or reason of failure)
     * \param errorMsg will be set to descriptive error string if the operation fails
     * \return the reshaped geometry
     *
     * \note Not available in Python bindings
     */
    SIP_SKIP std::unique_ptr< QgsAbstractGeometry > reshapeGeometry( const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg = nullptr ) const;

    /**
     * Merges any connected lines in a LineString/MultiLineString geometry and
     * converts them to single line strings.
     *
     * \param errorMsg will be set to descriptive error string if the operation fails
     * \returns a LineString or MultiLineString geometry, with any connected lines
     * joined. An empty geometry will be returned if the input geometry was not a
     * LineString/MultiLineString geometry.
     */
    std::unique_ptr< QgsAbstractGeometry > mergeLines( QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns the closest point on the geometry to the other geometry.
     *
     * \param other geometry to compare against
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns closest point
     *
     * \see shortestLine()
     */
    std::unique_ptr< QgsAbstractGeometry > closestPoint( const QgsGeometry &other, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns the shortest line joining this geometry to the other geometry.
     *
     * \param other geometry to compare against
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns shortest line joining this geometry to the other geometry
     *
     * \see closestPoint()
     */
    std::unique_ptr< QgsAbstractGeometry > shortestLine( const QgsGeometry &other, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns the shortest line joining this geometry to the other geometry.
     *
     * \param other geometry to compare against
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns shortest line joining this geometry to the other geometry
     *
     * \see closestPoint()
     * \since QGIS 3.20
     */
    std::unique_ptr< QgsAbstractGeometry > shortestLine( const QgsAbstractGeometry *other, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns a distance representing the location along this linestring of the closest point
     * on this linestring geometry to the specified point. Ie, the returned value indicates
     * how far along this linestring you need to traverse to get to the closest location
     * where this linestring comes to the specified point.
     *
     * \param point point to seek proximity to
     * \param errorMsg will be set to descriptive error string if the operation fails
     * \returns distance along line, or -1 on error
     * \note only valid for linestring geometries
     */
    double lineLocatePoint( const QgsPoint &point, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns a distance representing the location along this linestring of the closest point
     * on this linestring geometry to the point at (\a x, \a y). Ie, the returned value indicates
     * how far along this linestring you need to traverse to get to the closest location
     * where this linestring comes to the specified point.
     *
     * This method is more efficient than creating a temporary QgsPoint object to locate.
     *
     * \param x x-coordinate of point to locate
     * \param y y-coordinate of point to locate
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns distance along line, or -1 on error
     *
     * \note only valid for linestring geometries
     *
     * \since QGIS 3.26
     */
    double lineLocatePoint( double x, double y, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Creates a GeometryCollection geometry containing possible polygons formed from the constituent
     * linework of a set of \a geometries. The input geometries must be fully noded (i.e. nodes exist
     * at every common intersection of the geometries). The easiest way to ensure this is to first
     * unary union these geometries by calling combine() on the set of input geometries and then
     * pass the result to polygonize().
     * An empty geometry will be returned in the case of errors.
     *
     * \note Not available in Python bindings
     */
    SIP_SKIP static QgsGeometry polygonize( const QVector<const QgsAbstractGeometry *> &geometries, QString *errorMsg = nullptr );

    /**
     * Creates a Voronoi diagram for the nodes contained within the geometry.
     *
     * Returns the Voronoi polygons for the nodes contained within the geometry.
     * If \a extent is specified then it will be used as a clipping envelope for the diagram.
     * If no extent is set then the clipping envelope will be automatically calculated.
     * In either case the diagram will be clipped to the larger of the provided envelope
     * OR the envelope surrounding all input nodes.
     * The \a tolerance parameter specifies an optional snapping tolerance which can
     * be used to improve the robustness of the diagram calculation.
     * If \a edgesOnly is TRUE than line string boundary geometries will be returned
     * instead of polygons.
     * An empty geometry will be returned if the diagram could not be calculated.
     *
     * \param extent optional clipping envelope for the diagram
     * \param tolerance
     * \param edgesOnly
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns Voronoi diagram
     */
    std::unique_ptr< QgsAbstractGeometry > voronoiDiagram( const QgsAbstractGeometry *extent = nullptr, double tolerance = 0.0, bool edgesOnly = false, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns the Delaunay triangulation for the vertices of the geometry.
     * The \a tolerance parameter specifies an optional snapping tolerance which can
     * be used to improve the robustness of the triangulation.
     * If \a edgesOnly is TRUE than line string boundary geometries will be returned
     * instead of polygons.
     * An empty geometry will be returned if the diagram could not be calculated.
     *
     * \param tolerance
     * \param edgesOnly
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns Delaunay triangulation
     *
     * \see constrainedDelaunayTriangulation()
     */
    std::unique_ptr< QgsAbstractGeometry > delaunayTriangulation( double tolerance = 0.0, bool edgesOnly = false, QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Returns a constrained Delaunay triangulation for the vertices of the geometry.
     *
     * An empty geometry will be returned if the triangulation could not be calculated.
     *
     * This method requires a QGIS build based on GEOS 3.11 or later.
     *
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns constrained Delaunay triangulation
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.10 or earlier.
     * \see delaunayTriangulation()
     *
     * \since QGIS 3.36
     */
    std::unique_ptr< QgsAbstractGeometry > constrainedDelaunayTriangulation( QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Returns a possibly concave geometry that encloses the input geometry.
     *
     * The result is a single polygon, line or point.
     *
     * It will not contain holes unless the optional \a allowHoles argument is specified as true.
     *
     * One can think of a concave hull as a geometry obtained by "shrink-wrapping" a set of geometries.
     * This is different to the convex hull, which is more like wrapping a rubber band around the geometries.
     * It is slower to compute than the convex hull but generally has a smaller area and represents a more natural boundary for the input geometry.
     * The \a target_percent is the percentage of area of the convex hull the solution tries to approach.
     *
     * A \a target_percent of 1 gives the same result as the convex hull.
     * A \a target_percent between 0 and 0.99 produces a result that should have a smaller area than the convex hull.
     *
     * This method requires a QGIS build based on GEOS 3.11 or later.
     *
     * \param targetPercent
     * \param allowHoles
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns concave geometry that encloses the input geometry
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.10 or earlier.
     * \see convexHull()
     * \since QGIS 3.28
     */
    std::unique_ptr< QgsAbstractGeometry > concaveHull( double targetPercent, bool allowHoles = false, QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Analyze a coverage (represented as a collection of polygonal geometry with exactly matching edge
     * geometry) to find places where the assumption of exactly matching edges is not met.
     *
     * The input geometry is the polygonal coverage to access, stored in a geometry collection. All members must be POLYGON
     * or MULTIPOLYGON.
     *
     * \param gapWidth The maximum width of gaps to detect.
     * \param invalidEdges When there are invalidities in the coverage, this pointer will be set with a geometry
     * collection of the same length as the input, with a MULTILINESTRING of the error edges for each invalid polygon,
     * or an EMPTY where the polygon is a valid participant in the coverage. Pass NULLPTR if you do not want the invalid edges returned.
     * \param errorMsg
     *
     * This method requires a QGIS build based on GEOS 3.12 or later.
     *
     * \note Not available in Python bindings
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.11 or earlier.
     * \see simplifyCoverageVW()
     * \since QGIS 3.36
     */
    SIP_SKIP Qgis::CoverageValidityResult validateCoverage( double gapWidth, std::unique_ptr< QgsAbstractGeometry > *invalidEdges, QString *errorMsg = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Operates on a coverage (represented as a list of polygonal geometry with exactly matching edge geometry) to apply
     * a Visvalingam–Whyatt simplification to the edges, reducing complexity in proportion with the provided tolerance,
     * while retaining a valid coverage (no edges will cross or touch after the simplification).
     *
     * Geometries never disappear, but they may be simplified down to just a triangle. Also, some invalid geoms
     * (such as Polygons which have too few non-repeated points) will be returned unchanged.
     *
     * If the input dataset is not a valid coverage due to overlaps, it will still be simplified, but invalid topology
     * such as crossing edges will still be invalid.
     *
     * \param tolerance A tolerance parameter in linear units.
     * \param preserveBoundary Set to TRUE to preserve the outside edges of the coverage without simplification, or FALSE to allow them to be simplified.
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns simplified coverage
     *
     * This method requires a QGIS build based on GEOS 3.12 or later.
     *
     * \throws QgsNotSupportedException on QGIS builds based on GEOS 3.11 or earlier.
     * \see validateCoverage()
     * \since QGIS 3.36
     */
    std::unique_ptr< QgsAbstractGeometry > simplifyCoverageVW( double tolerance, bool preserveBoundary, QString *errorMsg SIP_OUT = nullptr ) const SIP_THROW( QgsNotSupportedException );

    /**
     * Optimized union algorithm for polygonal inputs that are correctly noded and do not overlap.
     * It may generate an error (returns NULLPTR) for inputs that do not satisfy this constraint,
     * however this is not guaranteed.
     *
     * The input geometry is the polygonal coverage to union, stored in a geometry collection.
     * All members must be POLYGON or MULTIPOLYGON.
     *
     * \param errorMsg will be set to descriptive error string if the operation fails
     *
     * \returns unioned coverage
     *
     * \see validateCoverage()
     * \since QGIS 3.36
     */
    std::unique_ptr< QgsAbstractGeometry > unionCoverage( QString *errorMsg SIP_OUT = nullptr ) const;

    /**
     * Create a geometry from a GEOSGeometry
     * \param geos GEOSGeometry. Ownership is NOT transferred.
     * \note Not available in Python bindings
     */
    SIP_SKIP static std::unique_ptr< QgsAbstractGeometry > fromGeos( const GEOSGeometry *geos );

    /**
     * \note Not available in Python bindings
     */
    SIP_SKIP static std::unique_ptr< QgsPolygon > fromGeosPolygon( const GEOSGeometry *geos );

    /**
     * Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destroy_r)
     * \param geometry geometry to convert to GEOS representation
     * \param precision The precision of the grid to which to snap the geometry vertices. If 0, no snapping is performed.
     * \param flags GEOS creation flags (since QGIS 3.40)
     * \note Not available in Python bindings
     */
    SIP_SKIP static geos::unique_ptr asGeos( const QgsGeometry &geometry, double precision = 0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlags() );

    /**
     * Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destroy_r)
     * \param geometry geometry to convert to GEOS representation
     * \param precision The precision of the grid to which to snap the geometry vertices. If 0, no snapping is performed.
     * \param flags GEOS creation flags (since QGIS 3.40)
     * \note The third parameter was prior to QGIS 3.40 a boolean which has been incorporated into the flag
     * \note Not available in Python bindings
     */
    SIP_SKIP static geos::unique_ptr asGeos( const QgsAbstractGeometry *geometry, double precision = 0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlags() );

    /**
     * \note Not available in Python bindings
     */
    SIP_SKIP static QgsPoint coordSeqPoint( const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM );

  private:

#ifdef SIP_RUN
    QgsGeos( const QgsGeos & );
#endif

    mutable geos::unique_ptr mGeos;
    geos::prepared_unique_ptr mGeosPrepared;
    double mPrecision = 0.0;

    enum Overlay
    {
      OverlayIntersection,
      OverlayDifference,
      OverlayUnion,
      OverlaySymDifference
    };

    enum Relation
    {
      RelationIntersects,
      RelationTouches,
      RelationCrosses,
      RelationWithin,
      RelationOverlaps,
      RelationContains,
      RelationDisjoint
    };

    //geos util functions
    void cacheGeos( Qgis::GeosCreationFlags flags ) const;

    /**
     * Returns a geometry representing the overlay operation with \a geom.
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by GEOS
     * \param op Overlay Operation
     * \param parameters can be used to specify parameters which control the overlay results (since QGIS 3.28)
     */
    std::unique_ptr< QgsAbstractGeometry > overlay( const QgsAbstractGeometry *geom, Overlay op, QString *errorMsg = nullptr, const QgsGeometryParameters &parameters = QgsGeometryParameters() ) const;
    bool relation( const QgsAbstractGeometry *geom, Relation r, QString *errorMsg = nullptr ) const;
    static GEOSCoordSequence *createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose = false );
    static std::unique_ptr< QgsLineString > sequenceToLinestring( const GEOSGeometry *geos, bool hasZ, bool hasM );
    static int numberOfGeometries( GEOSGeometry *g );
    static geos::unique_ptr nodeGeometries( const GEOSGeometry *splitLine, const GEOSGeometry *geom );
    int mergeGeometriesMultiTypeSplit( std::vector<geos::unique_ptr> &splitResult ) const;

    /**
     * Ownership of geoms is transferred
     */
    static geos::unique_ptr createGeosCollection( int typeId, std::vector<geos::unique_ptr> &geoms );

    static geos::unique_ptr createGeosPointXY( double x, double y, bool hasZ, double z, bool hasM, double m, int coordDims, double precision, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlags() );
    static geos::unique_ptr createGeosPoint( const QgsAbstractGeometry *point, int coordDims, double precision, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlags() );
    static geos::unique_ptr createGeosLinestring( const QgsAbstractGeometry *curve, double precision, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlags() );
    static geos::unique_ptr createGeosPolygon( const QgsAbstractGeometry *poly, double precision, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlags() );

    //utils for geometry split
    bool topologicalTestPointsSplit( const GEOSGeometry *splitLine, QgsPointSequence &testPoints, QString *errorMsg = nullptr ) const;
    geos::unique_ptr linePointDifference( GEOSGeometry *GEOSsplitPoint ) const;
    EngineOperationResult splitLinearGeometry( const GEOSGeometry *splitLine, QVector<QgsGeometry > &newGeometries, bool skipIntersectionCheck ) const;
    EngineOperationResult splitPolygonGeometry( const GEOSGeometry *splitLine, QVector<QgsGeometry > &newGeometries, bool skipIntersectionCheck ) const;

    //utils for reshape
    static geos::unique_ptr reshapeLine( const GEOSGeometry *line, const GEOSGeometry *reshapeLineGeos, double precision );
    static geos::unique_ptr reshapePolygon( const GEOSGeometry *polygon, const GEOSGeometry *reshapeLineGeos, double precision );
    static int lineContainedInLine( const GEOSGeometry *line1, const GEOSGeometry *line2 );
    static int pointContainedInLine( const GEOSGeometry *point, const GEOSGeometry *line );
    static int geomDigits( const GEOSGeometry *geom );
    void subdivideRecursive( const GEOSGeometry *currentPart, int maxNodes, int depth, QgsGeometryCollection *parts, const QgsRectangle &clipRect, double gridSize = -1 ) const;
};

/// @cond PRIVATE

#ifndef SIP_RUN

class GEOSException : public std::runtime_error
{
  public:
    explicit GEOSException( const QString &message )
      : std::runtime_error( message.toUtf8().constData() )
    {
    }
};

#endif

/// @endcond

#endif // QGSGEOS_H
