diff --git a/capi/CMakeLists.txt b/capi/CMakeLists.txt index 63c2d02ee2..804a10f52a 100644 --- a/capi/CMakeLists.txt +++ b/capi/CMakeLists.txt @@ -31,6 +31,16 @@ set(VERSION_MAJOR ${GEOS_VERSION_MAJOR}) set(VERSION_MINOR ${GEOS_VERSION_MINOR}) set(VERSION_PATCH ${GEOS_VERSION_PATCH}) +set(INPUT_CURVES_CONVERTED_TO_LINES "This function does not support curved types directly. If curve-to-line +* conversion parameters are registered with the context, curved +* inputs will be automatically converted to lines. Otherwise, the +* operation will fail.") +set(OUTPUT_LINES_CONVERTED_TO_CURVES "This function outputs linear geometry types only. If line-to-curve +* conversion parameters are registered with the context, and a +* curved input was provided, outputs will be automatically +* converted to curved types.") + + configure_file( ${CMAKE_CURRENT_LIST_DIR}/geos_c.h.in ${CMAKE_CURRENT_BINARY_DIR}/geos_c.h diff --git a/capi/geos_c.cpp b/capi/geos_c.cpp index 9a832a64ba..9294f9c126 100644 --- a/capi/geos_c.cpp +++ b/capi/geos_c.cpp @@ -15,6 +15,8 @@ * ***********************************************************************/ +#include +#include #include #include #include @@ -43,7 +45,9 @@ #define GEOSPreparedGeometry geos::geom::prep::PreparedGeometry #define GEOSClusterInfo geos::operation::cluster::Clusters #define GEOSCoordSequence geos::geom::CoordinateSequence +#define GEOSCurveToLineParams geos::algorithm::CurveToLineParams #define GEOSBufferParams geos::operation::buffer::BufferParameters +#define GEOSLineToCurveParams geos::algorithm::LineToCurveParams #define GEOSSTRtree geos::index::strtree::TemplateSTRtree #define GEOSWKTReader geos::io::WKTReader #define GEOSWKTWriter geos::io::WKTWriter @@ -217,6 +221,65 @@ extern "C" { return GEOSCoveredBy_r(handle, g1, g2); } + GEOSCurveToLineParams* + GEOSCurveToLineParams_create() + { + return new geos::algorithm::CurveToLineParams; + } + + int + GEOSCurveToLineParams_setTolerance(GEOSCurveToLineParams* params, int toleranceType, double toleranceValue) + { + return GEOSCurveToLineParams_setTolerance_r(handle, params, toleranceType, toleranceValue); + } + + void + GEOSCurveToLineParams_destroy(GEOSCurveToLineParams* params) + { + delete params; + } + + GEOSGeometry* + GEOSCurveToLine(const GEOSGeometry* g, const GEOSCurveToLineParams* params) + { + return GEOSCurveToLine_r(handle, g, params); + } + + GEOSGeometry* + GEOSLineToCurve(const GEOSGeometry* g, const GEOSLineToCurveParams* params) + { + return GEOSLineToCurve_r(handle, g, params); + } + + GEOSLineToCurveParams* + GEOSLineToCurveParams_create() + { + return new geos::algorithm::LineToCurveParams; + } + + void + GEOSLineToCurveParams_destroy(GEOSLineToCurveParams* params) + { + delete params; + } + + int + GEOSLineToCurveParams_setRadiusTolerance(GEOSLineToCurveParams* params, double tolerance) + { + return GEOSLineToCurveParams_setRadiusTolerance_r(handle, params, tolerance); + } + + int + GEOSLineToCurveParams_setMaxStepDegrees(GEOSLineToCurveParams* params, double tolerance) + { + return GEOSLineToCurveParams_setMaxStepDegrees_r(handle, params, tolerance); + } + + int + GEOSLineToCurveParams_setMaxAngleDifferenceDegrees(GEOSLineToCurveParams* params, double tolerance) + { + return GEOSLineToCurveParams_setMaxAngleDifferenceDegrees_r(handle, params, tolerance); + } //------------------------------------------------------------------- // low-level relate functions diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in index 630f1763f4..1307a6e538 100644 --- a/capi/geos_c.h.in +++ b/capi/geos_c.h.in @@ -188,6 +188,20 @@ typedef struct GEOSMakeValidParams_t GEOSMakeValidParams; */ typedef struct GEOSClusterInfo_t GEOSClusterInfo; +/** +* Parameter object for curve-to-line conversion +* \see GEOSCurveToLineParams_create() +* \see GEOSCurveToLineParams_destroy() +*/ +typedef struct GEOSCurveToLineParams_t GEOSCurveToLineParams; + +/** +* Parameter object for line-to-curve conversion +* \see GEOSLineToCurveParams_create() +* \see GEOSLineToCurveParams_destroy() +*/ +typedef struct GEOSLineToCurveParams_t GEOSLineToCurveParams; + #endif /** \cond */ @@ -495,6 +509,38 @@ extern GEOSMessageHandler_r GEOS_DLL GEOSContext_setErrorMessageHandler_r( GEOSMessageHandler_r ef, void *userData); +/** +* Set parameters to use for converting input curves to lines. +* +* These parameters will be used with this context whenever a curved geometry is +* provided to a function that only supports linear types. If `params` is NULL, +* no automatic conversion will be performed. +* +* \param extHandle the GEOS context +* \param params the curve conversion parameters +* +* \since 3.15 +*/ +extern void GEOS_DLL GEOSContext_setCurveToLineParams_r( + GEOSContextHandle_t extHandle, + const GEOSCurveToLineParams* params); + +/** +* Set parameters to use for converting output lines to curves. +* +* These parameters will be used with this context whenever a curved geometry is +* provided to a function that only supports linear types. If `params` is NULL, +* no automatic conversion will be performed. +* +* \param extHandle the GEOS context +* \param params the curve conversion parameters +* +* \since 3.15 +*/ +extern void GEOS_DLL GEOSContext_setLineToCurveParams_r( + GEOSContextHandle_t extHandle, + const GEOSLineToCurveParams* params); + /* ========== Coordinate Sequence functions ========== */ /** \see GEOSCoordSeq_create */ @@ -1799,6 +1845,53 @@ extern double GEOS_DLL GEOSGeom_getPrecision_r( GEOSContextHandle_t handle, const GEOSGeometry *g); +/** +* Curve-to-line tolerance types. +*/ +enum GEOSCurveToLineTolerance { + /** Maximum angle between successive vertices of a linearized arc */ + GEOS_CURVETOLINE_STEP_DEGREES = 0, + /** Maximum distance between an arc and its linearized approximation */ + GEOS_CURVETOLINE_MAX_DEVIATION = 1 +}; + +/** \see GEOSCurveToLine */ +extern GEOSGeometry GEOS_DLL *GEOSCurveToLine_r( + GEOSContextHandle_t handle, + const GEOSGeometry* g, + const GEOSCurveToLineParams* params); + +/** \see GEOSCurveToLineParams_setTolerance */ +extern int GEOS_DLL GEOSCurveToLineParams_setTolerance_r( + GEOSContextHandle_t extHandle, + GEOSCurveToLineParams* params, + int toleranceType, + double toleranceValue); + +/** \see GEOSLineToCurve */ +extern GEOSGeometry GEOS_DLL *GEOSLineToCurve_r( + GEOSContextHandle_t handle, + const GEOSGeometry* g, + const GEOSLineToCurveParams* params); + +/** \see GEOSLineToCurveParams_setRadiusTolerance */ +extern int GEOS_DLL GEOSLineToCurveParams_setRadiusTolerance_r( + GEOSContextHandle_t extHandle, + GEOSLineToCurveParams* params, + double tolerance); + +/** \see GEOSLineToCurveParams_setMaxStepDegrees */ +extern int GEOS_DLL GEOSLineToCurveParams_setMaxStepDegrees_r( + GEOSContextHandle_t extHandle, + GEOSLineToCurveParams* params, + double tolerance); + +/** \see GEOSLineToCurveParams_setMaxAngleDifferenceDegrees */ +extern int GEOS_DLL GEOSLineToCurveParams_setMaxAngleDifferenceDegrees_r( + GEOSContextHandle_t extHandle, + GEOSLineToCurveParams* params, + double tolerance); + /** \see GEOSGetNumInteriorRings */ extern int GEOS_DLL GEOSGetNumInteriorRings_r( GEOSContextHandle_t handle, @@ -3447,6 +3540,9 @@ and geometric quality. * Tests whether the input geometry is "simple". Mostly relevant for * linestrings. A "simple" linestring has no self-intersections. * Z and M values are not considered. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The geometry to test * \return 1 on true, 0 on false, 2 on exception * \since 2.2 @@ -3460,6 +3556,8 @@ extern char GEOS_DLL GEOSisSimple(const GEOSGeometry* g); * Caller has the responsibility to destroy 'location' with * GEOSGeom_destroy() * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The geometry to test * \param findAllLocations Whether to return all self-intersection locations, or just one * \param locations A pointer in which the location GEOSGeometry will be placed @@ -3482,6 +3580,8 @@ extern char GEOS_DLL GEOSisSimpleDetail( * * Z and M values are not considered. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The geometry to test * \return 1 on true, 0 on false, 2 on exception * \see geos::operation::valid::isValidOp @@ -3492,6 +3592,9 @@ extern char GEOS_DLL GEOSisValid(const GEOSGeometry* g); /** * Return the human readable reason a geometry is invalid, * "Valid Geometry" string otherwise, or NULL on exception. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The geometry to test * \return A string with the reason, NULL on exception. * Caller must GEOSFree() their result. @@ -3506,6 +3609,9 @@ extern char GEOS_DLL *GEOSisValidReason(const GEOSGeometry *g); * rules are broken. * Caller has the responsibility to destroy 'reason' with GEOSFree() * and 'location' with GEOSGeom_destroy() +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The geometry to test * \param flags A value from the \ref GEOSValidFlags enum * \param reason A pointer in which the reason string will be places @@ -3522,6 +3628,10 @@ extern char GEOS_DLL GEOSisValidDetail( /** * Repair an invalid geometry, returning a valid output. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g The geometry to repair * \return The repaired geometry. Caller must free with GEOSGeom_destroy(). * @@ -3533,6 +3643,10 @@ extern GEOSGeometry GEOS_DLL *GEOSMakeValid( /** * Repair an invalid geometry, returning a valid output, using the * indicated GEOSMakeValidMethods algorithm and options. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g is the geometry to test. * \param makeValidParams is a GEOSMakeValidParams with the desired parameters set on it. * \return A repaired geometry. Caller must free with GEOSGeom_destroy(). @@ -3603,6 +3717,8 @@ extern int GEOS_DLL GEOSMakeValidParams_setKeepCollapsed( * 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 calculated. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g the input geometry * \param d a double to which the result can be stored * \return 0 if no exception occurred. @@ -3617,6 +3733,8 @@ extern int GEOS_DLL GEOSMinimumClearance(const GEOSGeometry* g, double* d); * Returns a 2D LineString whose endpoints define the minimum clearance of a geometry. * If the geometry has no minimum clearance, an empty LineString will be returned. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g the input geometry * \return a linestring geometry, or NULL if an exception occurred. * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -3720,6 +3838,8 @@ extern int GEOS_DLL GEOSGeomGetLength( * Calculate the distance between two geometries. * Distance is calculated in the XY plane; Z and M values are ignored. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param[in] g1 Input geometry * \param[in] g2 Input geometry * \param[out] dist Pointer to be filled in with distance result. Positive @@ -3737,6 +3857,8 @@ extern int GEOS_DLL GEOSDistance( * within the given dist. * Distance is calculated in the XY plane; Z and M values are ignored. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g1 Input geometry * \param g2 Input geometry * \param dist The max distance @@ -3754,6 +3876,9 @@ extern char GEOS_DLL GEOSDistanceWithin( * indexed facet distance, which first indexes the geometries * internally, then calculates the distance. Useful when one * or both geometries is very large. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param[in] g1 Input geometry * \param[in] g2 Input geometry * \param[out] dist Pointer to be filled in with distance result @@ -3772,6 +3897,8 @@ extern int GEOS_DLL GEOSDistanceIndexed( * The first point comes from g1 geometry and the second point comes from g2. * Returned points will not have Z or M values. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param[in] g1 Input geometry * \param[in] g2 Input geometry * \return A coordinate sequence with the two points, or NULL on exception. @@ -3787,6 +3914,9 @@ extern GEOSCoordSequence GEOS_DLL *GEOSNearestPoints( * Calculate the Hausdorff distance between two geometries. * [Hausdorff distance](https://en.wikipedia.org/wiki/Hausdorff_distance) * is the largest distance between two geometries. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param[in] g1 Input geometry * \param[in] g2 Input geometry * \param[out] dist Pointer to be filled in with distance result @@ -3803,6 +3933,9 @@ extern int GEOS_DLL GEOSHausdorffDistance( /** * Calculate the Hausdorff distance between two geometries and return * the coordinates of the points that determine the distance. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param[in] g1 Input geometry * \param[in] g2 Input geometry * \param[out] dist Pointer to be filled in with distance result @@ -3828,6 +3961,9 @@ extern int GEOS_DLL GEOSHausdorffDistanceWithPoints( * by densifying the inputs before computation. * [Hausdorff distance](https://en.wikipedia.org/wiki/Hausdorff_distance) * is the largest distance between two geometries. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param[in] g1 Input geometry * \param[in] g2 Input geometry * \param[in] densifyFrac The largest % of the overall line length that @@ -3844,24 +3980,27 @@ extern int GEOS_DLL GEOSHausdorffDistanceDensify( double *dist); /** - * Calculate a more precise Hausdorff distance between two geometries, - * by densifying the inputs before computation and return the points - * that determine the distance. - * [Hausdorff distance](https://en.wikipedia.org/wiki/Hausdorff_distance) - * is the largest distance between two geometries. - * \param[in] g1 Input geometry - * \param[in] g2 Input geometry - * \param[in] densifyFrac The largest % of the overall line length that - * any given two point segment should be. - * \param[out] dist Pointer to be filled in with distance result - * \param[out] p1x X coordinate of point on g1 - * \param[out] p1y Y coordinate of point on g1 - * \param[out] p2x X coordinate of point on g2 - * \param[out] p2y Y coordinate of point on g2 - * \return 1 on success, 0 on exception. - * \see goes::algorithm::distance::DiscreteHausdorffDistance - * \since 3.15 - */ +* Calculate a more precise Hausdorff distance between two geometries, +* by densifying the inputs before computation and return the points +* that determine the distance. +* [Hausdorff distance](https://en.wikipedia.org/wiki/Hausdorff_distance) +* is the largest distance between two geometries. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* +* \param[in] g1 Input geometry +* \param[in] g2 Input geometry +* \param[in] densifyFrac The largest % of the overall line length that +* any given two point segment should be. +* \param[out] dist Pointer to be filled in with distance result +* \param[out] p1x X coordinate of point on g1 +* \param[out] p1y Y coordinate of point on g1 +* \param[out] p2x X coordinate of point on g2 +* \param[out] p2y Y coordinate of point on g2 +* \return 1 on success, 0 on exception. +* \see goes::algorithm::distance::DiscreteHausdorffDistance +* \since 3.15 +*/ extern int GEOS_DLL GEOSHausdorffDistanceDensifyWithPoints( const GEOSGeometry *g1, const GEOSGeometry *g2, @@ -3875,6 +4014,9 @@ extern int GEOS_DLL GEOSHausdorffDistanceDensifyWithPoints( * [Frechet distance](https://en.wikipedia.org/wiki/Fr%C3%A9chet_distance) * between two geometries, * a similarity measure for linear features. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param[in] g1 Input geometry * \param[in] g2 Input geometry * \param[out] dist Pointer to be filled in with distance result @@ -3894,6 +4036,9 @@ extern int GEOS_DLL GEOSFrechetDistance( * between two geometries, * a similarity measure for linear features. * The inputs can be densified to provide a more accurate result. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param[in] g1 Input geometry * \param[in] g2 Input geometry * \param[in] densifyFrac The largest % of the overall line length that @@ -3921,6 +4066,9 @@ extern int GEOS_DLL GEOSFrechetDistanceDensify( /** * Distance of point projected onto line from the start of the line. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param line linear target of projection * \param point point to be projected onto 'g' * \return distance along line that point projects to, -1 on exception @@ -3937,7 +4085,9 @@ extern double GEOS_DLL GEOSProject(const GEOSGeometry* line, * If applicable, the Z value of the returned point will be interpolated from the input line. * Since GEOS 3.15, the M value will also be interpolated. * -* If the input is not a LineString, NULL will be returned. +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* +* If the (converted) input is not a LineString, NULL will be returned. * * \param line linear target of projection * \param d distance from start of line to created point @@ -3952,6 +4102,9 @@ extern GEOSGeometry GEOS_DLL *GEOSInterpolate(const GEOSGeometry* line, * Project point to line and calculate the **proportion** of * the line the point is from the start. For example, a point that * projects to the middle of a line would be return 0.5. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param line linear target of projection * \param point the point to project * \return The proportion of the overall line length that the projected @@ -3968,7 +4121,9 @@ extern double GEOS_DLL GEOSProjectNormalized(const GEOSGeometry* line, * If applicable, the Z value of the returned point will be interpolated from the input line. * Since GEOS 3.15, the M value will also be interpolated. * -* If the input is not a LineString, NULL will be returned. +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* +* If the (converted) input is not a LineString, NULL will be returned. * * \param line linear target of projection * \param proportion The proportion from the start of line to created point @@ -3992,6 +4147,10 @@ extern GEOSGeometry GEOS_DLL *GEOSInterpolateNormalized( /** * Returns the intersection of two geometries: the set of points * that fall within **both** geometries. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g1 one of the geometries * \param g2 the other geometry * \return A newly allocated geometry of the intersection. NULL on exception. @@ -4020,6 +4179,10 @@ extern GEOSGeometry GEOS_DLL *GEOSIntersectionPrec(const GEOSGeometry* g1, const /** * Returns the difference of two geometries A and B: the set of points * that fall within A but **not** within B. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param ga the base geometry * \param gb the geometry to subtract from it * \return A newly allocated geometry of the difference. NULL on exception. @@ -4055,6 +4218,10 @@ extern GEOSGeometry GEOS_DLL *GEOSDifferencePrec( * Returns the symmetric difference of two geometries A and B: the set of points * that fall in A but **not** within B and the set of points that fall in B but * **not** in A. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param ga geometry A * \param gb geometry B * \return A newly allocated geometry of the symmetric difference. NULL on exception. @@ -4090,6 +4257,10 @@ extern GEOSGeometry GEOS_DLL *GEOSSymDifferencePrec( /** * Returns the union of two geometries A and B: the set of points * that fall in A **or** within B. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param ga geometry A * \param gb geometry B * \return A newly allocated geometry of the union. NULL on exception. @@ -4125,6 +4296,10 @@ extern GEOSGeometry GEOS_DLL *GEOSUnionPrec( * Returns the union of all components of a single geometry. Usually * used to convert a collection into the smallest set of polygons * that cover the same area. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g The input geometry * \return A newly allocated geometry of the union. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -4157,6 +4332,10 @@ extern GEOSGeometry GEOS_DLL *GEOSUnaryUnionPrec( * Optimized union algorithm for inputs that can be divided into subsets * that do not intersect. If there is only one such subset, performance * can be expected to be worse than GEOSUnionaryUnion. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g The input geometry * \return A newly allocated geometry of the union, or NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -4169,6 +4348,10 @@ extern GEOSGeometry GEOS_DLL *GEOSDisjointSubsetUnion(const GEOSGeometry *g); * Intersection optimized for a rectangular clipping polygon. * Supposed to be faster than using GEOSIntersection(). Not * guaranteed to return valid results. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g The input geometry to be clipped * \param xmin Left bound of clipping rectangle * \param ymin Lower bound of clipping rectangle @@ -4188,6 +4371,10 @@ extern GEOSGeometry GEOS_DLL *GEOSClipByRect( /** * Compute the intersection of a geometry with each polygon in * a rectangular grid. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g The input geometry to be clipped * \param xmin Left bound of grd * \param ymin Lower bound of grid @@ -4218,6 +4405,9 @@ extern GEOSGeometry GEOS_DLL *GEOSSubdivideByGrid( /** * Determine the fraction of each cell in a rectangular grid * that is covered by a polygon. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The input geometry * \param xmin Left bound of grd * \param ymin Lower bound of grid @@ -4248,6 +4438,8 @@ extern int GEOS_DLL GEOSGridIntersectionFractions( * - second element is a MultiLineString containing shared paths * having the _opposite_ direction on the two inputs * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g1 An input geometry * \param g2 An input geometry * \return The shared paths @@ -4272,36 +4464,42 @@ static const size_t GEOS_CLUSTER_NONE = (size_t) -1; ///@{ /** - * @brief GEOSClusterDBSCAN - * - * Cluster geometries using the DBSCAN algorithm - * - * @param g a collection of geometries to be clustered - * @param eps distance parameter for clustering - * @param minPoints density parameter for clustering - * @return cluster information object - */ +* @brief GEOSClusterDBSCAN +* +* Cluster geometries using the DBSCAN algorithm +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* +* @param g a collection of geometries to be clustered +* @param eps distance parameter for clustering +* @param minPoints density parameter for clustering +* @return cluster information object +*/ extern GEOSClusterInfo GEOS_DLL* GEOSClusterDBSCAN(const GEOSGeometry* g, double eps, unsigned minPoints); /** - * @brief GEOSClusterGeometryDistance - * - * Cluster geometries according to a distance threshold - * - * @param g a collection of geometries to be clustered - * @param d minimum distance between geometries in the same cluster - * @return cluster information object - */ +* @brief GEOSClusterGeometryDistance +* +* Cluster geometries according to a distance threshold +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* +* @param g a collection of geometries to be clustered +* @param d minimum distance between geometries in the same cluster +* @return cluster information object +*/ extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryDistance(const GEOSGeometry* g, double d); /** - * @brief GEOSClusterGeometryIntersects - * - * Cluster geometries that intersect - * - * @param g a collection of geometries to be clustered - * @return cluster information object - */ +* @brief GEOSClusterGeometryIntersects +* +* Cluster geometries that intersect +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* +* @param g a collection of geometries to be clustered +* @return cluster information object +*/ extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryIntersects(const GEOSGeometry* g); /** @@ -4309,6 +4507,8 @@ extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryIntersects(const GEOSGeometr * * Cluster geometries according to an envelope distance threshold * + * This function supports curved geometry types. + * * @param g a collection of geometries to be clustered * @param d minimum envelope distance between geometries in the same cluster * @return cluster information object @@ -4320,6 +4520,8 @@ extern GEOSClusterInfo GEOS_DLL* GEOSClusterEnvelopeDistance(const GEOSGeometry* * * Cluster geometries whose envelopes intersect * + * This function supports curved geometry types. + * * @param g * @return cluster information object */ @@ -4388,6 +4590,8 @@ extern void GEOS_DLL GEOSClusterInfo_destroy(GEOSClusterInfo* clusters); * Buffer a geometry. * The coordinates of the constructed geometry will not have Z or M values. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The input geometry to be buffered. * \param width The distance by which to expand the geometry (or contract) * if the value is negative. @@ -4485,6 +4689,9 @@ extern int GEOS_DLL GEOSBufferParams_setSingleSided( /** * Generates a buffer using the special parameters in the GEOSBufferParams +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The geometry to buffer * \param p The parameters to apply to the buffer process * \param width The buffer distance @@ -4502,6 +4709,8 @@ extern GEOSGeometry GEOS_DLL *GEOSBufferWithParams( * Generate a buffer using the provided style parameters. * The coordinates of the constructed geometry will not have Z or M values. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The geometry to buffer * \param width Width of the buffer * \param quadsegs Number of segments per quadrant @@ -4526,6 +4735,8 @@ extern GEOSGeometry GEOS_DLL *GEOSBufferWithStyle( * Handles all non-curved geometry types as input. * The coordinates of the constructed geometry will not have Z or M values. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * - For a LineString the result is a LineString. * - For a Point the result is an empty LineString. * - For a Polygon the result is the boundary lines(s) of the polygon buffered to the offset distance @@ -4562,6 +4773,10 @@ extern GEOSGeometry GEOS_DLL *GEOSOffsetCurve(const GEOSGeometry* g, * noded and do not overlap. It may generate an error (return NULL) * for inputs that do not satisfy this constraint, however this is not * guaranteed. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g The input geometry * \return A geometry that covers all the points of the input geometry. * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -4576,6 +4791,8 @@ extern GEOSGeometry GEOS_DLL *GEOSCoverageUnion(const GEOSGeometry *g); * with exactly matching edge geometry) to find places where the * assumption of exactly matching edges is not met. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param input The polygonal coverage to access, * stored in a geometry collection. All members must be POLYGON * or MULTIPOLYGON. @@ -4612,6 +4829,9 @@ extern int GEOS_DLL GEOSCoverageIsValid( * it will still be simplified, but invalid topology such as crossing * edges will still be invalid. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param input The polygonal coverage to access, * stored in a geometry collection. All members must be POLYGON * or MULTIPOLYGON. @@ -4734,6 +4954,9 @@ GEOSCoverageCleanParams_setOverlapMergeStrategy( * the input. Polygons that have collapsed during cleaning will be returned * as empties. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param input The dirty polygonal coverage, * stored in a geometry collection. All members must be POLYGON * or MULTIPOLYGON. @@ -4809,6 +5032,8 @@ extern GEOSGeometry GEOS_DLL *GEOSBoundary(const GEOSGeometry* g); * Z values in the input coordinates will be preserved. * M values in the input coordinates will not be preserved. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The input geometry * \return A newly allocated geometry of the convex hull. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -4836,6 +5061,8 @@ extern GEOSGeometry GEOS_DLL *GEOSConvexHull(const GEOSGeometry* g); * This can be expressed as a ratio between the lengths of the longest and shortest edges. * 1 produces the convex hull; 0 produces a hull with maximum concaveness * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The input geometry * \param ratio The edge length ratio value, between 0 and 1. * \param allowHoles When non-zero, the polygonal output may contain holes. @@ -4870,9 +5097,11 @@ extern GEOSGeometry GEOS_DLL *GEOSConcaveHull( * determined by a numeric target parameter. * The concave hull is constructed by removing the longest outer edges * of the Delaunay Triangulation of the space between the polygons, -* until the specified maximm edge length is reached. +* until the specified maximum edge length is reached. * A large value produces the convex hull, 0 produces the hull of maximum concaveness. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The input geometry * \param length The maximum edge length (0 or greater) * \param allowHoles When non-zero, the polygonal output may contain holes. @@ -4911,6 +5140,8 @@ extern GEOSGeometry GEOS_DLL *GEOSConcaveHullByLength( * The input polygons *must* be a *valid* MultiPolygon * (i.e. they must be non-overlapping). * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g the valid MultiPolygon geometry to process * \param lengthRatio specifies the Maximum Edge Length as a * fraction of the difference between the longest and @@ -4945,6 +5176,9 @@ extern GEOSGeometry GEOS_DLL *GEOSConcaveHullOfPolygons( * An outer hull is computed if the parameter is positive, * an inner hull is computed if it is negative. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g the polygonal geometry to process * \param isOuter indicates whether to compute an outer or inner hull (1 for outer hull, 0 for inner) * \param vertexNumFraction the target fraction of the count of input vertices to retain in result @@ -4979,6 +5213,9 @@ enum GEOSPolygonHullParameterModes { * produces the original geometry. * Either outer or inner hulls can be computed. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param g the polygonal geometry to process * \param isOuter indicates whether to compute an outer or inner hull (1 for outer hull, 0 for inner) * \param parameterMode the interpretation to apply to the parameter argument; see \ref GEOSPolygonHullParameterModes @@ -5005,6 +5242,9 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonHullSimplifyMode( * the input is degenerate (a line or point) a linestring or point * is returned. The minimum rotated rectangle can be used as an * extremely generalized representation for the given geometry. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The input geometry * \return A newly allocated geometry of the rotated envelope. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -5029,6 +5269,9 @@ extern GEOSGeometry GEOS_DLL *GEOSMinimumRotatedRectangle(const GEOSGeometry* g) * 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. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g Input geometry * \param tolerance Stop the algorithm when the search area is smaller than this tolerance * \return A newly allocated geometry of the MIC. NULL on exception. @@ -5054,17 +5297,19 @@ extern GEOSGeometry GEOS_DLL *GEOSMaximumInscribedCircle( * the obstacles (up to the given distance tolerance). * The LEC is determined by the center point and a point indicating the circle radius * (which will lie on an obstacle). -* \n +* * To compute an LEC which lies **wholly** within a polygonal boundary, * include the boundary of the polygon(s) as a linear obstacle. -* \n +* * 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. -* \n +* * Returns the LEC radius as a two-point linestring, with the start point at the center of the inscribed circle and the end * on the boundary of the circle. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param obstacles The geometries that the LEC must not cross * \param boundary The area to contain the LEC center (may be null or empty) * \param tolerance Stop the algorithm when the search area is smaller than this tolerance @@ -5085,6 +5330,9 @@ extern GEOSGeometry GEOS_DLL *GEOSLargestEmptyCircle( * 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. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The input geometry * \return A newly allocated geometry of the LEC. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -5097,6 +5345,9 @@ extern GEOSGeometry GEOS_DLL *GEOSMinimumWidth(const GEOSGeometry* g); /** * Returns a 2D point that is inside the boundary of a polygonal geometry. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The input geometry * \return A point that is inside the input * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -5108,6 +5359,9 @@ extern GEOSGeometry GEOS_DLL *GEOSPointOnSurface(const GEOSGeometry* g); /** * Returns a 2D point at the center of mass of the input. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g The input geometry * \return A point at the center of mass of the input * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -5120,6 +5374,9 @@ extern GEOSGeometry GEOS_DLL *GEOSGetCentroid(const GEOSGeometry* g); /** * Returns a 2D geometry which represents the "minimum bounding circle", * the smallest circle that contains the input. +* +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param[in] g The input geometry * \param[out] radius Pointer will be filled with output radius. * \param[out] center Pointer will be filled with output circle center. Caller must free. @@ -5140,6 +5397,9 @@ extern GEOSGeometry GEOS_DLL *GEOSMinimumBoundingCircle( * Z values from the input points will be propagated to the generated polygons. * M values from the input points will be ignored. * +* Curved geometries are supported since GEOS 3.15. For curved geometries, +* the control point and endpoints of each arc will be used as input vertices. +* * \param g the input geometry whose vertices will be used as "sites" * \param tolerance optional snapping tolerance to use for improved robustness * \param onlyEdges if non-zero will return a MultiLineString, otherwise it will @@ -5163,6 +5423,8 @@ extern GEOSGeometry GEOS_DLL * GEOSDelaunayTriangulation( * Z values from the input points will be propagated to the generated polygons. * M values from the input points will be ignored. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* * \param g the input geometry whose rings will be used as input * \return A newly allocated geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -5187,6 +5449,9 @@ enum GEOSVoronoiFlags * Returns the 2D Voronoi polygons or edges computed from the vertices * of the given geometry. * +* Curved geometries are supported since GEOS 3.15. For curved geometries, +* the control point and endpoints of each arc will be used as input vertices. +* * \param g the input geometry whose vertices will be used as sites. * \param tolerance snapping tolerance to use for improved robustness. A tolerance of 0.0 specifies that no snapping will take @@ -5272,6 +5537,9 @@ extern GEOSGeometry GEOS_DLL *GEOSNode(const GEOSGeometry* g); * that the input lines form a valid polygonal geometry (which may * include holes or nested polygons). * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param geoms Array of linear geometries to polygons. Caller retains ownership of both array container and objects. * \param ngeoms Size of the geoms array. * \return The polygonal output geometry. @@ -5289,6 +5557,9 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonize( * but returns a result which is a valid polygonal geometry. * The result will not contain any edge-adjacent elements. * +* @INPUT_CURVES_CONVERTED_TO_LINES@ +* @OUTPUT_LINES_CONVERTED_TO_CURVES@ +* * \param geoms Array of linear geometries to polygons. Caller retains ownership of both array container and objects. * \param ngeoms Size of the geoms array. * \return The polygonal output geometry. @@ -5307,7 +5578,7 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonize_valid( * "cut edges", the linear features that are connected at both ends, * do *not* participate in the final polygon. * -* \param geoms Array of linear geometries to polygons. Caller retains ownersihp of both array container and objects. +* \param geoms Array of linear geometries to polygons. Caller retains ownership of both array container and objects. * \param ngeoms Size of the geoms array. * \return The "cut edges" * Caller is responsible for freeing with GEOSGeom_destroy(). @@ -5367,6 +5638,11 @@ extern GEOSGeometry GEOS_DLL *GEOSBuildArea(const GEOSGeometry* g); * evenly subdivide that segment. Z and M values of added * vertices will be interpolated. * Only linear components of input geometry are densified. +* +* Curved geometries are not supported, unless curve-to-line parameters are +* registered with the context handle. Output geometries will not contain +* curves. +* * \param g The geometry to densify * \param tolerance the distance tolerance to densify * \return The densified geometry, or NULL on exception. @@ -5513,10 +5789,13 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_extractUniquePoints( /** * Calculate the * [Hilbert code](https://en.wikipedia.org/wiki/Hilbert_curve) -* of the centroid of a geometry relative to an extent. +* of the center of a geometry's bounding box relative to a larger extent. * This allows sorting geometries in a deterministic way, such that similar Hilbert codes are * likely to be near each other in two-dimensional space. * The caller must ensure that the geometry is contained within the extent. +* +* Curved geometries are supported since GEOS 3.13. +* * \param[in] geom Input geometry, must be non-empty * \param[in] extent Extent within which to calculate the Hilbert code for geom * \param[in] level The level of precision of the Hilbert curve, up to 16 @@ -5640,6 +5919,110 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_setPrecision( ///@} +/* ============================================================== */ + +/** @name Line/Curve conversion +* Functions for converting between curved and linear geometry types. +*/ +///@{ + +/** Approximate all circular arcs in a geometry using line segments. +* If the input has no circular arcs, a copy will be returned. +* \param g the geometry to convert +* \param params parameters to use in the linearization. +* \return The linearized geometry. +* Caller must free with GEOSGeom_destroy() +* NULL on exception +* +* \since 3.15 +*/ +extern GEOSGeometry GEOS_DLL *GEOSCurveToLine( + const GEOSGeometry* g, + const GEOSCurveToLineParams* params); + +extern GEOSCurveToLineParams GEOS_DLL *GEOSCurveToLineParams_create(); + +extern void GEOS_DLL GEOSCurveToLineParams_destroy(GEOSCurveToLineParams* params); + +/** Set the tolerance type and value to use when converting curves to lines. +* +* \param params parameters to use in the conversion. +* \param toleranceType \see GEOSCurveToLineTolerance +* \param toleranceValue the value to use with toleranceType +* \return 1 if the operation succeeded, 0 otherwise* +* +* \since 3.15 +*/ +extern int GEOS_DLL GEOSCurveToLineParams_setTolerance( + GEOSCurveToLineParams* params, + int toleranceType, + double toleranceValue); + +/** Replace line segments in the geometry with circular arcs, where possible. +* +* \param g the geometry to convert +* \param params parameters to use in the conversion. +* \return The converted geometry. +* Caller must free with GEOSGeom_destroy() +* NULL on exception +* +* \since 3.15 +*/ +extern GEOSGeometry GEOS_DLL *GEOSLineToCurve( + const GEOSGeometry* g, + const GEOSLineToCurveParams* params); + +extern GEOSLineToCurveParams GEOS_DLL *GEOSLineToCurveParams_create(); + +extern void GEOS_DLL GEOSLineToCurveParams_destroy(GEOSLineToCurveParams* params); + +/** Set the radius tolerance used in line-to-curve conversion. +* +* A vertex may be considered to continue a circular arc if its distance to the +* previously-computed circle center is within the specified fraction of the +* circle radius. +* +* \param params the parameters object +* \param tolerance the tolerance value +* \return 1 on success, 0 on failure +* \since 3.15 +*/ +extern int GEOS_DLL GEOSLineToCurveParams_setRadiusTolerance( + GEOSLineToCurveParams* params, + double tolerance); + +/** Set the maximum angle spacing used in line-to-curve conversion. +* +* A vertex may be considered to continue a circular arc if the circular sector +* between the vertex and the final point in the arc is less than the specified tolerance. Because of round-off errors, this value should be slightly greater than the one used +* in \ref GEOSCurveToLineParams_setTolerance. +* +* \param params the parameters object +* \param tolerance the tolerance value +* \return 1 on success, 0 on failure +* \since 3.15 +*/ +extern int GEOS_DLL GEOSLineToCurveParams_setMaxStepDegrees( + GEOSLineToCurveParams* params, + double tolerance); + +/** Set the maximum angle difference used in line-to-curve conversion +* +* A vertex p[n] may be considered to continue a circular arc if the +* difference between the angles p[n-2] / p[n-1] / p[n] is within the +* specified difference of the angle p[0] / p[1] / p[2]. +* +* \param params the parameters object +* \param tolerance the tolerance value +* \return 1 on success, 0 on failure +* \since 3.15 +*/ +extern int GEOS_DLL GEOSLineToCurveParams_setMaxAngleDifferenceDegrees( + GEOSLineToCurveParams* params, + double tolerance); + +///@} + /* ============================================================== */ /** @name Spatial Predicates * Functions computing binary spatial predicates using the DE-9IM topology model. diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp index e57a003433..e346a4bb44 100644 --- a/capi/geos_ts_c.cpp +++ b/capi/geos_ts_c.cpp @@ -18,6 +18,8 @@ ***********************************************************************/ #include +#include +#include #include #include #include @@ -128,6 +130,7 @@ #include #include #include +#include #include #include #include @@ -145,7 +148,9 @@ #define GEOSPreparedGeometry geos::geom::prep::PreparedGeometry #define GEOSClusterInfo geos::operation::cluster::Clusters #define GEOSCoordSequence geos::geom::CoordinateSequence +#define GEOSCurveToLineParams geos::algorithm::CurveToLineParams #define GEOSBufferParams geos::operation::buffer::BufferParameters +#define GEOSLineToCurveParams geos::algorithm::LineToCurveParams #define GEOSSTRtree geos::index::strtree::TemplateSTRtree #define GEOSWKTReader geos::io::WKTReader #define GEOSWKTWriter geos::io::WKTWriter @@ -252,6 +257,8 @@ typedef struct GEOSContextHandle_HS { int WKBByteOrder; int initialized; std::unique_ptr point2d; + std::optional lineToCurveParams; + std::optional curveToLineParams; GEOSContextHandle_HS() : @@ -388,6 +395,81 @@ typedef struct GEOSContextHandle_HS { } } GEOSContextHandleInternal_t; +class InputGeometry { + +public: + explicit InputGeometry(const Geometry* geom) : + m_geom(const_cast(geom)), m_owned(false) {} + + explicit InputGeometry(std::unique_ptr geom) : + m_geom(geom.release()), m_owned(true) {} + + ~InputGeometry() { + if (m_owned) { + delete m_geom; + } + } + + operator const Geometry*() const { + return m_geom; + } + + const Geometry* get() const { + return m_geom; + } + + const Geometry* operator->() const { + return m_geom; + } + + bool isLinearized() const { + return m_owned; + } + + InputGeometry(const InputGeometry&) = delete; + InputGeometry& operator=(const InputGeometry&) = delete; + + InputGeometry(InputGeometry&& other) noexcept : m_geom(other.m_geom), m_owned(other.m_owned) { + other.m_geom = nullptr; + other.m_owned = false; + } + InputGeometry& operator=(InputGeometry&& other) noexcept { + m_geom = other.m_geom; + m_owned = other.m_owned; + other.m_geom = nullptr; + other.m_owned = false; + + return *this; + } + +private: + Geometry* m_geom; + bool m_owned; +}; + +static bool +isCurvedType (geos::geom::GeometryTypeId typ) { + return typ == geos::geom::GeometryTypeId::GEOS_CIRCULARSTRING || + typ == geos::geom::GeometryTypeId::GEOS_COMPOUNDCURVE || + typ == geos::geom::GeometryTypeId::GEOS_CURVEPOLYGON || + typ == geos::geom::GeometryTypeId::GEOS_MULTICURVE || + typ == geos::geom::GeometryTypeId::GEOS_MULTISURFACE; +} + +InputGeometry convertToLineIfNeeded(GEOSContextHandle_t extHandle, const Geometry* g) { + if (extHandle->curveToLineParams.has_value() && g != nullptr && (isCurvedType(g->getGeometryTypeId()) || g->hasCurvedComponents())) { + return InputGeometry(g->getLinearized(extHandle->curveToLineParams.value())); + } + return InputGeometry(g); +} + +Geometry* convertToCurveIfNeeded(GEOSContextHandle_t extHandle, std::unique_ptr g) { + if (extHandle->lineToCurveParams.has_value() && g->hasCurvedComponents()) { + return g->getCurved(extHandle->lineToCurveParams.value()).release(); + } + return g.release(); +} + // CAPI_ItemVisitor is used internally by the CAPI STRtree // wrappers. It's defined here just to keep it out of the // extern "C" block. @@ -547,6 +629,44 @@ inline void execute(GEOSContextHandle_t extHandle, F&& f) { } } +template +Geometry* convertCurvesAndExecute(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, F&& f) +{ + return execute(extHandle, [&]() { + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + auto result = f(geom1.get(), geom2.get()); + + if (result) { + if (extHandle->lineToCurveParams && (geom1.isLinearized() || geom2.isLinearized())) { + result = result->getCurved(extHandle->lineToCurveParams.value()); + } + + result->setSRID(g1->getSRID()); + } + + return result.release(); + }); +} + +template +Geometry* convertCurvesAndExecute(GEOSContextHandle_t extHandle, const Geometry* g1, F&& f) +{ + return execute(extHandle, [&]() { + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + + auto g3 = f(geom1.get()); + + if (extHandle->lineToCurveParams && (geom1.isLinearized())) { + g3 = g3->getCurved(extHandle->lineToCurveParams.value()); + } + + g3->setSRID(g1->getSRID()); + return g3.release(); + }); +} + extern "C" { GEOSContextHandle_t @@ -627,6 +747,24 @@ extern "C" { return handle->setInterruptHandler(cb, userData); } + void GEOSContext_setCurveToLineParams_r(GEOSContextHandle_t extHandle, const GEOSCurveToLineParams* params) + { + if (params) { + extHandle->curveToLineParams = *params; + } else { + extHandle->curveToLineParams = std::nullopt; + } + } + + void GEOSContext_setLineToCurveParams_r(GEOSContextHandle_t extHandle, const GEOSLineToCurveParams* params) + { + if (params) { + extHandle->lineToCurveParams = *params; + } else { + extHandle->lineToCurveParams = std::nullopt; + } + } + void finishGEOS_r(GEOSContextHandle_t extHandle) { @@ -659,7 +797,10 @@ extern "C" { GEOSDisjoint_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->disjoint(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->disjoint(geom2); }); } @@ -667,7 +808,10 @@ extern "C" { GEOSTouches_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->touches(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->touches(geom2); }); } @@ -675,7 +819,10 @@ extern "C" { GEOSIntersects_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->intersects(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->intersects(geom2); }); } @@ -683,7 +830,10 @@ extern "C" { GEOSCrosses_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->crosses(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->crosses(geom2); }); } @@ -691,7 +841,10 @@ extern "C" { GEOSWithin_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->within(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->within(geom2); }); } @@ -699,7 +852,10 @@ extern "C" { GEOSContains_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->contains(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->contains(geom2); }); } @@ -707,7 +863,10 @@ extern "C" { GEOSOverlaps_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->overlaps(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->overlaps(geom2); }); } @@ -715,7 +874,10 @@ extern "C" { GEOSCovers_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->covers(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->covers(geom2); }); } @@ -723,7 +885,10 @@ extern "C" { GEOSCoveredBy_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->coveredBy(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->coveredBy(geom2); }); } @@ -731,7 +896,10 @@ extern "C" { GEOSEquals_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->equals(g2); + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + + return geom1->equals(geom2); }); } @@ -744,8 +912,11 @@ extern "C" { GEOSRelatePattern_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, const char* imPattern) { return execute(extHandle, 2, [&]() { + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + std::string s(imPattern); - return g1->relate(g2, s); + return geom1->relate(geom2, s); }); } @@ -768,9 +939,12 @@ extern "C" { GEOSRelate_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, [&]() { + const auto geom1 = convertToLineIfNeeded(extHandle, g1); + const auto geom2 = convertToLineIfNeeded(extHandle, g2); + using geos::geom::IntersectionMatrix; - auto im = g1->relate(g2); + auto im = geom1->relate(geom2); if(im == nullptr) { return (char*) nullptr; } @@ -780,7 +954,7 @@ extern "C" { } char* - GEOSRelateBoundaryNodeRule_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, int bnr) + GEOSRelateBoundaryNodeRule_r(GEOSContextHandle_t extHandle, const Geometry* geom1, const Geometry* geom2, int bnr) { using geos::operation::relate::RelateOp; using geos::geom::IntersectionMatrix; @@ -788,6 +962,8 @@ extern "C" { return execute(extHandle, [&]() -> char* { std::unique_ptr im; + const auto g1 = convertToLineIfNeeded(extHandle, geom1); + const auto g2 = convertToLineIfNeeded(extHandle, geom2); switch (bnr) { case GEOSRELATE_BNR_MOD2: /* same as OGC */ @@ -835,9 +1011,11 @@ extern "C" { return execute(extHandle, 2, [&]() { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + using geos::operation::valid::IsValidOp; - IsValidOp ivo(g1); + IsValidOp ivo(inputGeom); const TopologyValidationError* err = ivo.getValidationError(); if(err) { @@ -854,12 +1032,14 @@ extern "C" { GEOSisValidReason_r(GEOSContextHandle_t extHandle, const Geometry* g1) { return execute(extHandle, [&]() { + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + using geos::operation::valid::IsValidOp; char* result = nullptr; char const* const validstr = "Valid Geometry"; - IsValidOp ivo(g1); + IsValidOp ivo(inputGeom); const TopologyValidationError* err = ivo.getValidationError(); if(err) { @@ -886,7 +1066,9 @@ extern "C" { using geos::operation::valid::IsValidOp; return execute(extHandle, 2, [&]() { - IsValidOp ivo(g); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + + IsValidOp ivo(inputGeom); if(flags & GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE) { ivo.setSelfTouchingRingFormingHoleValid(true); } @@ -937,7 +1119,10 @@ extern "C" { GEOSDistance_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double* dist) { return execute(extHandle, 0, [&]() { - *dist = g1->distance(g2); + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + *dist = input1->distance(input2); return 1; }); } @@ -946,7 +1131,10 @@ extern "C" { GEOSDistanceWithin_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double dist) { return execute(extHandle, 2, [&]() { - return g1->isWithinDistance(g2, dist); + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + return input1->isWithinDistance(input2, dist); }); } @@ -954,7 +1142,10 @@ extern "C" { GEOSDistanceIndexed_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double* dist) { return execute(extHandle, 0, [&]() { - *dist = IndexedFacetDistance::distance(g1, g2); + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + *dist = IndexedFacetDistance::distance(input1, input2); return 1; }); } @@ -963,7 +1154,10 @@ extern "C" { GEOSHausdorffDistance_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double* dist) { return execute(extHandle, 0, [&]() { - *dist = DiscreteHausdorffDistance::distance(*g1, *g2); + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + *dist = DiscreteHausdorffDistance::distance(*input1, *input2); return 1; }); } @@ -975,7 +1169,10 @@ extern "C" { double* p2x, double* p2y) { return execute(extHandle, 0, [&]() { - DiscreteHausdorffDistance dhd(*g1, *g2); + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + DiscreteHausdorffDistance dhd(*input1, *input2); *dist = dhd.distance(); const auto& pts = dhd.getCoordinates(); *p1x = pts[0].x; @@ -991,7 +1188,10 @@ extern "C" { double densifyFrac, double* dist) { return execute(extHandle, 0, [&]() { - *dist = DiscreteHausdorffDistance::distance(*g1, *g2, densifyFrac); + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + *dist = DiscreteHausdorffDistance::distance(*input1, *input2, densifyFrac); return 1; }); } @@ -1001,7 +1201,10 @@ extern "C" { double densifyFrac, double* dist, double* p1x, double* p1y, double* p2x, double* p2y) { return execute(extHandle, 0, [&]() { - DiscreteHausdorffDistance dhd(*g1, *g2); + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + DiscreteHausdorffDistance dhd(*input1, *input2); dhd.setDensifyFraction(densifyFrac); *dist = dhd.distance(); const auto& pts = dhd.getCoordinates(); @@ -1017,7 +1220,10 @@ extern "C" { GEOSFrechetDistance_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double* dist) { return execute(extHandle, 0, [&]() { - *dist = DiscreteFrechetDistance::distance(*g1, *g2); + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + *dist = DiscreteFrechetDistance::distance(*input1, *input2); return 1; }); } @@ -1027,7 +1233,10 @@ extern "C" { double* dist) { return execute(extHandle, 0, [&]() { - *dist = DiscreteFrechetDistance::distance(*g1, *g2, densifyFrac); + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + *dist = DiscreteFrechetDistance::distance(*input1, *input2, densifyFrac); return 1; }); } @@ -1057,7 +1266,11 @@ extern "C" { if(g1->isEmpty() || g2->isEmpty()) { return nullptr; } - return geos::operation::distance::DistanceOp::nearestPoints(g1, g2).release(); + + const auto input1 = convertToLineIfNeeded(extHandle, g1); + const auto input2 = convertToLineIfNeeded(extHandle, g2); + + return geos::operation::distance::DistanceOp::nearestPoints(input1, input2).release(); }); } @@ -1077,8 +1290,8 @@ extern "C" { { return execute(extHandle, [&]() { geos::operation::cluster::DBSCANClusterFinder finder(eps, minPoints); - - return capi_clusters(g, finder); + const auto input = convertToLineIfNeeded(extHandle, g); + return capi_clusters(input, finder); }); } @@ -1087,7 +1300,8 @@ extern "C" { { return execute(extHandle, [&]() { geos::operation::cluster::GeometryIntersectsClusterFinder finder; - return capi_clusters(g, finder); + const auto input = convertToLineIfNeeded(extHandle, g); + return capi_clusters(input, finder); }); } @@ -1114,7 +1328,8 @@ extern "C" { { return execute(extHandle, [&]() { geos::operation::cluster::GeometryDistanceClusterFinder finder(d); - return capi_clusters(g, finder); + const auto input = convertToLineIfNeeded(extHandle, g); + return capi_clusters(input, finder); }); } @@ -1274,7 +1489,7 @@ extern "C" { GEOSisSimple_r(GEOSContextHandle_t extHandle, const Geometry* g1) { return execute(extHandle, 2, [&]() { - return g1->isSimple(); + return convertToLineIfNeeded(extHandle, g1)->isSimple(); }); } @@ -1282,7 +1497,9 @@ extern "C" { GEOSisSimpleDetail_r(GEOSContextHandle_t extHandle, const Geometry* g1, int returnAllPoints, Geometry** result) { return execute(extHandle, 2, [&]() { - geos::operation::valid::IsSimpleOp iso(g1); + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + + geos::operation::valid::IsSimpleOp iso(inputGeom); iso.setFindAllLocations(returnAllPoints); *result = nullptr; @@ -1305,7 +1522,8 @@ extern "C" { GEOSisRing_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, 2, [&]() { - const Curve* ls = dynamic_cast(g); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + const Curve* ls = dynamic_cast(inputGeom.get()); if(ls) { return ls->isRing(); } @@ -1353,10 +1571,8 @@ extern "C" { Geometry* GEOSIntersection_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { - return execute(extHandle, [&]() { - auto g3 = g1->intersection(g2); - g3->setSRID(g1->getSRID()); - return g3.release(); + return convertCurvesAndExecute(extHandle, g1, g2, [](const Geometry* geom1, const Geometry* geom2) { + return geom1->intersection(geom2); }); } @@ -1384,7 +1600,9 @@ extern "C" { GEOSBuffer_r(GEOSContextHandle_t extHandle, const Geometry* g1, double width, int quadrantsegments) { return execute(extHandle, [&]() { - auto g3 = g1->buffer(width, quadrantsegments); + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + + auto g3 = inputGeom->buffer(width, quadrantsegments); g3->setSRID(g1->getSRID()); return g3.release(); }); @@ -1399,6 +1617,8 @@ extern "C" { using geos::util::IllegalArgumentException; return execute(extHandle, [&]() { + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + BufferParameters bp; bp.setQuadrantSegments(quadsegs); @@ -1416,7 +1636,7 @@ extern "C" { static_cast(joinStyle) ); bp.setMitreLimit(mitreLimit); - BufferOp op(g1, bp); + BufferOp op(inputGeom, bp); std::unique_ptr g3 = op.getResultGeometry(width); g3->setSRID(g1->getSRID()); return g3.release(); @@ -1429,7 +1649,9 @@ extern "C" { using geos::geom::util::Densifier; return execute(extHandle, [&]() { - Densifier densifier(g); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + + Densifier densifier(inputGeom); densifier.setDistanceTolerance(tolerance); auto g3 = densifier.getResultGeometry(); g3->setSRID(g->getSRID()); @@ -1442,6 +1664,8 @@ extern "C" { double mitreLimit) { return execute(extHandle, [&]() { + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + BufferParameters bp; //-- use default cap style ROUND bp.setQuadrantSegments(quadsegs); @@ -1454,7 +1678,7 @@ extern "C" { ); bp.setMitreLimit(mitreLimit); - OffsetCurve oc(*g1, width, bp); + OffsetCurve oc(*inputGeom, width, bp); std::unique_ptr g3 = oc.getCurve(); g3->setSRID(g1->getSRID()); return g3.release(); @@ -1491,7 +1715,8 @@ extern "C" { GEOSConvexHull_r(GEOSContextHandle_t extHandle, const Geometry* g1) { return execute(extHandle, [&]() { - auto g3 = g1->convexHull(); + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + auto g3 = inputGeom->convexHull(); g3->setSRID(g1->getSRID()); return g3.release(); }); @@ -1504,7 +1729,9 @@ extern "C" { unsigned int allowHoles) { return execute(extHandle, [&]() { - ConcaveHull hull(g1); + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + + ConcaveHull hull(inputGeom); hull.setMaximumEdgeLengthRatio(ratio); hull.setHolesAllowed(allowHoles); std::unique_ptr g3 = hull.getHull(); @@ -1520,7 +1747,9 @@ extern "C" { unsigned int allowHoles) { return execute(extHandle, [&]() { - ConcaveHull hull(g1); + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + + ConcaveHull hull(inputGeom); hull.setMaximumEdgeLength(length); hull.setHolesAllowed(allowHoles); std::unique_ptr g3 = hull.getHull(); @@ -1535,10 +1764,8 @@ extern "C" { unsigned int isOuter, double vertexNumFraction) { - return execute(extHandle, [&]() { - std::unique_ptr g3 = PolygonHullSimplifier::hull(g1, isOuter, vertexNumFraction); - g3->setSRID(g1->getSRID()); - return g3.release(); + return convertCurvesAndExecute(extHandle, g1, [&](const GEOSGeometry* inputGeom) { + return PolygonHullSimplifier::hull(inputGeom, isOuter, vertexNumFraction); }); } @@ -1549,16 +1776,12 @@ extern "C" { unsigned int parameterMode, double parameter) { - return execute(extHandle, [&]() { + return convertCurvesAndExecute(extHandle, g1, [&](const Geometry* inputGeom) { if (parameterMode == GEOSHULL_PARAM_AREA_RATIO) { - std::unique_ptr g3 = PolygonHullSimplifier::hullByAreaDelta(g1, isOuter, parameter); - g3->setSRID(g1->getSRID()); - return g3.release(); + return PolygonHullSimplifier::hullByAreaDelta(inputGeom, isOuter, parameter); } else if (parameterMode == GEOSHULL_PARAM_VERTEX_RATIO) { - std::unique_ptr g3 = PolygonHullSimplifier::hull(g1, isOuter, parameter); - g3->setSRID(g1->getSRID()); - return g3.release(); + return PolygonHullSimplifier::hull(inputGeom, isOuter, parameter); } else { throw IllegalArgumentException("GEOSPolygonHullSimplifyMode_r: Unknown parameterMode"); @@ -1574,9 +1797,11 @@ extern "C" { unsigned int isHolesAllowed) { return execute(extHandle, [&]() { + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + std::unique_ptr g3 = ConcaveHullOfPolygons::concaveHullByLengthRatio( - g1, lengthRatio, + inputGeom, lengthRatio, isTight > 0, isHolesAllowed > 0); g3->setSRID(g1->getSRID()); @@ -1590,7 +1815,9 @@ extern "C" { using geos::algorithm::MinimumAreaRectangle; return execute(extHandle, [&]() { - auto g3 = MinimumAreaRectangle::getMinimumRectangle(g); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + + auto g3 = MinimumAreaRectangle::getMinimumRectangle(inputGeom); g3->setSRID(g->getSRID()); return g3.release(); }); @@ -1600,7 +1827,9 @@ extern "C" { GEOSMaximumInscribedCircle_r(GEOSContextHandle_t extHandle, const Geometry* g, double tolerance) { return execute(extHandle, [&]() { - geos::algorithm::construct::MaximumInscribedCircle mic(g, tolerance); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + + geos::algorithm::construct::MaximumInscribedCircle mic(inputGeom, tolerance); auto g3 = mic.getRadiusLine(); g3->setSRID(g->getSRID()); return g3.release(); @@ -1611,7 +1840,10 @@ extern "C" { GEOSLargestEmptyCircle_r(GEOSContextHandle_t extHandle, const Geometry* g, const GEOSGeometry* boundary, double tolerance) { return execute(extHandle, [&]() { - geos::algorithm::construct::LargestEmptyCircle lec(g, boundary, tolerance); + const auto obstacles = convertToLineIfNeeded(extHandle, g); + const auto bounds = convertToLineIfNeeded(extHandle, boundary); + + geos::algorithm::construct::LargestEmptyCircle lec(obstacles, bounds, tolerance); auto g3 = lec.getRadiusLine(); g3->setSRID(g->getSRID()); return g3.release(); @@ -1622,7 +1854,9 @@ extern "C" { GEOSMinimumWidth_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, [&]() { - geos::algorithm::MinimumDiameter m(g); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + + geos::algorithm::MinimumDiameter m(inputGeom); auto g3 = m.getDiameter(); g3->setSRID(g->getSRID()); return g3.release(); @@ -1633,7 +1867,8 @@ extern "C" { GEOSMinimumClearanceLine_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, [&]() { - geos::precision::MinimumClearance mc(g); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + geos::precision::MinimumClearance mc(inputGeom); auto g3 = mc.getLine(); g3->setSRID(g->getSRID()); return g3.release(); @@ -1644,7 +1879,8 @@ extern "C" { GEOSMinimumClearance_r(GEOSContextHandle_t extHandle, const Geometry* g, double* d) { return execute(extHandle, 2, [&]() { - geos::precision::MinimumClearance mc(g); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + geos::precision::MinimumClearance mc(inputGeom); double res = mc.getDistance(); *d = res; return 0; @@ -1655,10 +1891,8 @@ extern "C" { Geometry* GEOSDifference_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { - return execute(extHandle, [&]() { - auto g3 = g1->difference(g2); - g3->setSRID(g1->getSRID()); - return g3.release(); + return convertCurvesAndExecute(extHandle, g1, g2, [](const Geometry* geom1, const Geometry* geom2) { + return geom1->difference(geom2); }); } @@ -1696,10 +1930,8 @@ extern "C" { Geometry* GEOSSymDifference_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { - return execute(extHandle, [&]() { - auto g3 = g1->symDifference(g2); - g3->setSRID(g1->getSRID()); - return g3.release(); + return convertCurvesAndExecute(extHandle, g1, g2, [&](const Geometry* input1, const Geometry* input2) { + return input1->symDifference(input2); }); } @@ -1727,10 +1959,8 @@ extern "C" { Geometry* GEOSUnion_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { - return execute(extHandle, [&]() { - auto g3 = g1->Union(g2); - g3->setSRID(g1->getSRID()); - return g3.release(); + return convertCurvesAndExecute(extHandle, g1, g2, [&](const Geometry* input1, const Geometry* input2) { + return input1->Union(input2); }); } @@ -1758,30 +1988,24 @@ extern "C" { Geometry* GEOSCoverageUnion_r(GEOSContextHandle_t extHandle, const Geometry* g) { - return execute(extHandle, [&]() { - auto g3 = geos::coverage::CoverageUnion::Union(g); - g3->setSRID(g->getSRID()); - return g3.release(); + return convertCurvesAndExecute(extHandle, g, [&](const Geometry* inputGeom) { + return geos::coverage::CoverageUnion::Union(inputGeom); }); } Geometry* GEOSDisjointSubsetUnion_r(GEOSContextHandle_t extHandle, const Geometry* g) { - return execute(extHandle, [&]() { - auto g3 = geos::operation::geounion::DisjointSubsetUnion::Union(g); - g3->setSRID(g->getSRID()); - return g3.release(); + return convertCurvesAndExecute(extHandle, g, [&](const Geometry* inputGeom) { + return geos::operation::geounion::DisjointSubsetUnion::Union(inputGeom); }); } Geometry* GEOSUnaryUnion_r(GEOSContextHandle_t extHandle, const Geometry* g) { - return execute(extHandle, [&]() { - std::unique_ptr g3(g->Union()); - g3->setSRID(g->getSRID()); - return g3.release(); + return convertCurvesAndExecute(extHandle, g, [&](const Geometry* input1) { + return input1->Union(); }); } @@ -1835,7 +2059,7 @@ extern "C" { GEOSPointOnSurface_r(GEOSContextHandle_t extHandle, const Geometry* g1) { return execute(extHandle, [&]() { - auto ret = g1->getInteriorPoint(); + auto ret = convertToLineIfNeeded(extHandle, g1)->getInteriorPoint(); ret->setSRID(g1->getSRID()); return ret.release(); }); @@ -1844,13 +2068,13 @@ extern "C" { Geometry* GEOSClipByRect_r(GEOSContextHandle_t extHandle, const Geometry* g, double xmin, double ymin, double xmax, double ymax) { - return execute(extHandle, [&]() { + return convertCurvesAndExecute(extHandle, g, [&](const Geometry* inputGeom) { using geos::operation::intersection::Rectangle; using geos::operation::intersection::RectangleIntersection; Rectangle rect(xmin, ymin, xmax, ymax); - std::unique_ptr g3 = RectangleIntersection::clip(*g, rect); + std::unique_ptr g3 = RectangleIntersection::clip(*inputGeom, rect); g3->setSRID(g->getSRID()); - return g3.release(); + return g3; }); } @@ -1858,13 +2082,13 @@ extern "C" { GEOSSubdivideByGrid_r(GEOSContextHandle_t extHandle, const Geometry* g, double xmin, double ymin, double xmax, double ymax, unsigned nx, unsigned ny, int include_exterior) { - return execute(extHandle, [&]() { + return convertCurvesAndExecute(extHandle, g, [&](const Geometry* inputGeom) { Envelope env(xmin, xmax, ymin, ymax); double dx = env.getWidth() / static_cast(nx); double dy = env.getHeight() / static_cast(ny); geos::operation::grid::Grid grid(env, dx, dy); - return geos::operation::grid::GridIntersection::subdividePolygon(grid, *g, include_exterior).release(); + return geos::operation::grid::GridIntersection::subdividePolygon(grid, *inputGeom, include_exterior); }); } @@ -1873,6 +2097,8 @@ extern "C" { double xmax, double ymax, unsigned nx, unsigned ny, float* buf) { return execute(extHandle, 0, [&]() { + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + Envelope env(xmin, xmax, ymin, ymax); double dx = env.getWidth() / static_cast(nx); double dy = env.getHeight() / static_cast(ny); @@ -1883,7 +2109,7 @@ extern "C" { std::shared_ptr bufPtr(buf, [](float*) {}); auto cov = std::make_shared>(ny, nx, bufPtr); - geos::operation::grid::GridIntersection isect(grid, *g, cov); + geos::operation::grid::GridIntersection isect(grid, *inputGeom, cov); return 1; }); @@ -2285,7 +2511,9 @@ extern "C" { GEOSGetCentroid_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, [&]() -> Geometry* { - auto ret = g->getCentroid(); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); + + auto ret = inputGeom->getCentroid(); ret->setSRID(g->getSRID()); return ret.release(); }); @@ -2312,8 +2540,9 @@ extern "C" { { return execute(extHandle, [&]() -> Geometry* { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); + const auto inputGeom = convertToLineIfNeeded(extHandle, g); - geos::algorithm::MinimumBoundingCircle mc(g); + geos::algorithm::MinimumBoundingCircle mc(inputGeom); std::unique_ptr ret = mc.getCircle(); const GeometryFactory* gf = handle->geomFactory; if (center) *center = gf->createPoint(mc.getCentre()).release(); @@ -2442,13 +2671,29 @@ extern "C" { // Polygonize Polygonizer plgnzr; + std::vector> linearized; + for(std::size_t i = 0; i < ngeoms; ++i) { - plgnzr.add(g[i]); + if (handle->curveToLineParams && g[i]->hasCurvedComponents()) { + linearized.push_back(g[i]->getLinearized(handle->curveToLineParams.value())); + plgnzr.add(linearized.back().get()); + } else { + plgnzr.add(g[i]); + } } - auto polys = plgnzr.getPolygons(); const GeometryFactory* gf = handle->geomFactory; - return gf->createGeometryCollection(std::move(polys)).release(); + auto polys = plgnzr.getPolygons(); + if (linearized.empty() || !handle->lineToCurveParams) { + return gf->createGeometryCollection(std::move(polys)).release(); + } else { + std::vector > curved(polys.size()); + for (std::size_t i = 0; i < polys.size(); i++) { + curved[i] = polys[i]->getCurved(handle->lineToCurveParams.value()); + polys[i].reset(); + } + return gf->createGeometryCollection(std::move(curved)).release(); + } }); } @@ -2459,27 +2704,52 @@ extern "C" { return execute(extHandle, [&]() -> Geometry* { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); - Geometry* out; + std::unique_ptr out; // Polygonize Polygonizer plgnzr(true); + std::vector> linearized; int srid = 0; for(std::size_t i = 0; i < ngeoms; ++i) { - plgnzr.add(g[i]); + if (handle->curveToLineParams && g[i]->hasCurvedComponents()) { + linearized.push_back(g[i]->getLinearized(handle->curveToLineParams.value())); + plgnzr.add(linearized.back().get()); + } else { + plgnzr.add(g[i]); + } srid = g[i]->getSRID(); } auto polys = plgnzr.getPolygons(); if (polys.empty()) { - out = handle->geomFactory->createGeometryCollection().release(); + out = handle->geomFactory->createGeometryCollection(); } else if (polys.size() == 1) { - return polys[0].release(); + if (linearized.empty() || !handle->lineToCurveParams) { + out = std::move(polys[0]); + } else { + out = polys[0]->getCurved(handle->lineToCurveParams.value()); + } } else { - return handle->geomFactory->createMultiPolygon(std::move(polys)).release(); + if (linearized.empty() || !handle->lineToCurveParams) { + out = handle->geomFactory->createMultiPolygon(std::move(polys)); + } else { + std::vector > curved(polys.size()); + bool curvedOutput = false; + for (std::size_t i = 0; i < polys.size(); i++) { + curved[i] = polys[i]->getCurved(handle->lineToCurveParams.value()); + polys[i].reset(); + curvedOutput |= curved[i]->hasCurvedComponents(); + } + if (curvedOutput) { + out = handle->geomFactory->createMultiSurface(std::move(curved)); + } else { + out = handle->geomFactory->createMultiPolygon(std::move(curved)); + } + } } out->setSRID(srid); - return out; + return out.release(); }); } @@ -2550,33 +2820,25 @@ extern "C" { Geometry* GEOSMakeValidWithParams_r( GEOSContextHandle_t extHandle, - const Geometry* g, + const Geometry* input, const GEOSMakeValidParams* params) { using geos::geom::util::GeometryFixer; using geos::operation::valid::MakeValid; - if (params && params->method == GEOS_MAKE_VALID_LINEWORK) { - return execute(extHandle, [&]() { + return convertCurvesAndExecute(extHandle, input, [params](const Geometry* g) { + if (params && params->method == GEOS_MAKE_VALID_LINEWORK) { MakeValid makeValid; - auto out = makeValid.build(g); - out->setSRID(g->getSRID()); - return out.release(); - }); - } - else if (params && params->method == GEOS_MAKE_VALID_STRUCTURE) { - return execute(extHandle, [&]() { + return makeValid.build(g); + } + if (params && params->method == GEOS_MAKE_VALID_STRUCTURE) { GeometryFixer fixer(g); fixer.setKeepCollapsed(params->keepCollapsed == 0 ? false : true); - auto out = fixer.getResult(); - out->setSRID(g->getSRID()); - return out.release(); - }); - } - else { - extHandle->ERROR_MESSAGE("Unknown method in GEOSMakeValidParams"); - return nullptr; - } + return fixer.getResult(); + } + + throw IllegalArgumentException("Unknown method in GEOSMakeValidParams"); + }); } Geometry* @@ -3502,6 +3764,61 @@ extern "C" { }); } + GEOSGeometry* + GEOSCurveToLine_r(GEOSContextHandle_t extHandle, const Geometry *g, + const GEOSCurveToLineParams* params) + { + return execute(extHandle, [&]() { + return g->getLinearized(*params).release(); + }); + } + + int + GEOSCurveToLineParams_setTolerance_r(GEOSContextHandle_t extHandle, GEOSCurveToLineParams* params, + int toleranceType, double toleranceValue) + { + return execute(extHandle, 0, [&]() { + params->setTolerance(static_cast(toleranceType), toleranceValue); + return 1; + }); + } + + GEOSGeometry* + GEOSLineToCurve_r(GEOSContextHandle_t extHandle, const GEOSGeometry* g, + const GEOSLineToCurveParams* params) + { + return execute(extHandle, [&]() { + return g->getCurved(*params).release(); + }); + } + + int + GEOSLineToCurveParams_setMaxAngleDifferenceDegrees_r(GEOSContextHandle_t extHandle, GEOSLineToCurveParams* params, double tolerance) + { + return execute(extHandle, 0, [&]() { + params->setMaxExteriorAngleDifferenceDegrees(tolerance); + return 1; + }); + } + + int + GEOSLineToCurveParams_setMaxStepDegrees_r(GEOSContextHandle_t extHandle, GEOSLineToCurveParams* params, double tolerance) + { + return execute(extHandle, 0, [&]() { + params->setMaxStepDegrees(tolerance); + return 1; + }); + } + + int + GEOSLineToCurveParams_setRadiusTolerance_r(GEOSContextHandle_t extHandle, GEOSLineToCurveParams* params, double tolerance) + { + return execute(extHandle, 0, [&]() { + params->setRadiusTolerance(tolerance); + return 1; + }); + } + int GEOSGeom_getDimensions_r(GEOSContextHandle_t extHandle, const Geometry* g) { @@ -4284,7 +4601,9 @@ extern "C" { throw std::runtime_error("third argument of GEOSProject_r must be Point"); } const geos::geom::Coordinate inputPt(*p->getCoordinate()); - return geos::linearref::LengthIndexedLine(g).project(inputPt); + const auto inputLine = convertToLineIfNeeded(extHandle, g); + + return geos::linearref::LengthIndexedLine(inputLine).project(inputPt); }); } @@ -4295,8 +4614,9 @@ extern "C" { return execute(extHandle, [&]() { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); const GeometryFactory* gf = handle->geomFactory; + const auto inputGeom = convertToLineIfNeeded(extHandle, g); - geos::linearref::LengthIndexedLine lil(g); + geos::linearref::LengthIndexedLine lil(inputGeom); CoordinateXYZM coord = lil.extractPoint(d); std::unique_ptr point; @@ -4319,24 +4639,22 @@ extern "C" { GEOSProjectNormalized_r(GEOSContextHandle_t extHandle, const Geometry* g, const Geometry* p) { + return execute(extHandle, -1, [&]() { + const auto inputLine = convertToLineIfNeeded(extHandle, g); - double length; - double distance; - if(GEOSLength_r(extHandle, g, &length) != 1) { - return -1.0; - }; - - distance = GEOSProject_r(extHandle, g, p); + double length = inputLine->getLength(); + double distance = GEOSProject_r(extHandle, inputLine.get(), p); - if (distance == 0.0 && length == 0.0) - return 0.0; + if (distance == 0.0 && length == 0.0) + return 0.0; - /* Meaningless projection? error */ - if (distance < 0.0 || ! std::isfinite(distance) || length == 0.0) { - return -1.0; - } else { - return distance / length; - } + /* Meaningless projection? error */ + if (distance < 0.0 || ! std::isfinite(distance) || length == 0.0) { + return -1.0; + } else { + return distance / length; + } + }); } @@ -4407,7 +4725,12 @@ extern "C" { SharedPathsOp::PathList forw, back; try { - SharedPathsOp::sharedPathsOp(*g1, *g2, forw, back); + const auto inputGeom1 = convertToLineIfNeeded(handle, g1); + const auto inputGeom2 = convertToLineIfNeeded(handle, g2); + + SharedPathsOp::sharedPathsOp(*inputGeom1, *inputGeom2, forw, back); + // Result will be a MultiLineString of line segments. + // Geometry::getCurved() will pass it through unmodified. } catch(const std::exception& e) { SharedPathsOp::clearEdges(forw); @@ -4551,7 +4874,9 @@ extern "C" { using geos::operation::buffer::BufferOp; return execute(extHandle, [&]() { - BufferOp op(g1, *bp); + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + + BufferOp op(inputGeom, *bp); std::unique_ptr g3 = op.getResultGeometry(width); g3->setSRID(g1->getSRID()); return g3.release(); @@ -4587,7 +4912,9 @@ extern "C" { using geos::triangulate::polygon::ConstrainedDelaunayTriangulator; return execute(extHandle, [&]() -> Geometry* { - return ConstrainedDelaunayTriangulator::triangulate(g1).release(); + const auto inputGeom = convertToLineIfNeeded(extHandle, g1); + + return ConstrainedDelaunayTriangulator::triangulate(inputGeom).release(); }); } @@ -4649,7 +4976,9 @@ extern "C" { using geos::coverage::CoverageValidator; return execute(extHandle, 2, [&]() { - const GeometryCollection* col = dynamic_cast(input); + const auto linearized = convertToLineIfNeeded(extHandle, input); + + const GeometryCollection* col = dynamic_cast(linearized.get()); if (!col) throw geos::util::IllegalArgumentException("input is not a collection"); @@ -4691,8 +5020,8 @@ extern "C" { { using geos::coverage::CoverageSimplifier; - return execute(extHandle, [&]() -> Geometry* { - const GeometryCollection* col = dynamic_cast(input); + return convertCurvesAndExecute(extHandle, input, [&](const Geometry* linearized) -> std::unique_ptr { + const GeometryCollection* col = dynamic_cast(linearized); if (!col) return nullptr; @@ -4711,8 +5040,7 @@ extern "C" { else return nullptr; const GeometryFactory* gf = input->getFactory(); - std::unique_ptr r = gf->createGeometryCollection(std::move(simple)); - return r.release(); + return gf->createGeometryCollection(std::move(simple)); }); } @@ -4786,8 +5114,8 @@ extern "C" { { using geos::coverage::CoverageCleaner; - return execute(extHandle, [&]() -> Geometry* { - const GeometryCollection* col = dynamic_cast(input); + return convertCurvesAndExecute(extHandle, input, [params](const Geometry* linearized) -> std::unique_ptr { + const GeometryCollection* col = dynamic_cast(linearized); if (!col) return nullptr; @@ -4804,9 +5132,8 @@ extern "C" { c.clean(); auto cleanCov = c.getResult(); - const GeometryFactory* gf = input->getFactory(); - std::unique_ptr r = gf->createGeometryCollection(std::move(cleanCov)); - return r.release(); + const GeometryFactory* gf = linearized->getFactory(); + return gf->createGeometryCollection(std::move(cleanCov)); }); } diff --git a/include/geos/algorithm/CurveBuilder.h b/include/geos/algorithm/CurveBuilder.h new file mode 100644 index 0000000000..1739dc5408 --- /dev/null +++ b/include/geos/algorithm/CurveBuilder.h @@ -0,0 +1,66 @@ +/********************************************************************** +* + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2025-2026 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +#include + +namespace geos::algorithm { + class LineToCurveParams; +} + +namespace geos::geom { + class CircularArc; + class CoordinateSequence; + class Curve; + class GeometryFactory; + class LineString; + class SimpleCurve; +} + +namespace geos::algorithm { + +class GEOS_DLL CurveBuilder { + +public: + + static std::unique_ptr getCurved(const geom::LineString& ls, const LineToCurveParams& params); + +private: + + explicit CurveBuilder(const geom::GeometryFactory& factory); + + std::unique_ptr compute(const geom::LineString& ls, const LineToCurveParams& params); + + void addArc(const geom::CircularArc& arc, std::size_t stop); + + void addLineCoords(const geom::CoordinateSequence& points, std::size_t from, std::size_t to); + + void finishArc(); + + void finishLine(); + + std::shared_ptr lineCoords; + std::shared_ptr arcCoords; + std::vector> curves; + const geom::GeometryFactory& factory; + + /// Declared as non-copyable + CurveBuilder(const CurveBuilder& other); + CurveBuilder& operator=(const CurveBuilder& rhs); +}; + +} \ No newline at end of file diff --git a/include/geos/algorithm/CurveToLineParams.h b/include/geos/algorithm/CurveToLineParams.h new file mode 100644 index 0000000000..9e1ba5debe --- /dev/null +++ b/include/geos/algorithm/CurveToLineParams.h @@ -0,0 +1,80 @@ +/********************************************************************** +* + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2026 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +namespace geos::algorithm { + +class GEOS_DLL CurveToLineParams { + +public: + enum class TOLERANCE_TYPE { + STEP_DEGREES, + MAX_DEVIATION, + }; + + CurveToLineParams() : CurveToLineParams(TOLERANCE_TYPE::STEP_DEGREES, 4.0) {} + + CurveToLineParams(TOLERANCE_TYPE tolType, double tolValue) { + setTolerance(tolType, tolValue); + } + + void setTolerance(TOLERANCE_TYPE tolType, double tolValue) { + if (tolType == TOLERANCE_TYPE::STEP_DEGREES) { + if (!(tolValue > 0)) { + throw util::IllegalArgumentException("Step size must be positive"); + } + } else if (tolType == TOLERANCE_TYPE::MAX_DEVIATION) { + if (!(tolValue > 0)) { + throw util::IllegalArgumentException("Max deviation must be positive"); + } + } else { + throw util::IllegalArgumentException("Invalid tolerance type"); + } + + toleranceType = tolType; + toleranceValue = tolValue; + } + + static CurveToLineParams maxDeviation(double dev) { + return CurveToLineParams(TOLERANCE_TYPE::MAX_DEVIATION, dev); + } + + static CurveToLineParams stepSizeDegrees(double stepSize) { + return CurveToLineParams(TOLERANCE_TYPE::STEP_DEGREES, stepSize); + } + + double getStepSizeDegrees(const geom::CircularArc& arc) const { + if (toleranceType == TOLERANCE_TYPE::STEP_DEGREES) { + return toleranceValue; + } + + if (toleranceType == TOLERANCE_TYPE::MAX_DEVIATION) { + return std::acos(1 - toleranceValue / arc.getRadius()) * 360 / MATH_PI; + } + + throw util::IllegalArgumentException("Invalid tolerance type"); + } + +private: + + TOLERANCE_TYPE toleranceType; + double toleranceValue; + +}; + +} \ No newline at end of file diff --git a/include/geos/algorithm/LineToCurveParams.h b/include/geos/algorithm/LineToCurveParams.h new file mode 100644 index 0000000000..9690df289b --- /dev/null +++ b/include/geos/algorithm/LineToCurveParams.h @@ -0,0 +1,75 @@ +/********************************************************************** +* + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2026 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +namespace geos::algorithm { + +class GEOS_DLL LineToCurveParams { + +public: + + // Get the radius tolerance. For p[x] to be considered a continuation of the same arc, + // its radius must be within tol * the arc radius as computed from p[0], p[1], p[2]. + double getRadiusTolerance() const { + return radiusTolerance; + } + + void setRadiusTolerance(double tol) { + if (!(tol > 0)) { + throw util::IllegalArgumentException("Radius tolerance must be positive"); + } + radiusTolerance = tol; + } + + double getMaxExteriorAngleDifferenceRadians() const { + return maxExteriorAngleDifferenceRadians; + } + + void setMaxExteriorAngleDifferenceRadians(double tol) { + if (!(tol > 0)) { + throw util::IllegalArgumentException("Angle tolerance must be positive"); + } + maxExteriorAngleDifferenceRadians = tol; + } + + void setMaxExteriorAngleDifferenceDegrees(double tol) { + setMaxExteriorAngleDifferenceRadians(tol * MATH_PI / 180.0); + } + + // Get the maximum angle step. + double getMaxAngleDegrees() const { + return maxAngleRadians * 180 / MATH_PI; + } + + double getMaxAngleRadians() const { + return maxAngleRadians; + } + + void setMaxStepDegrees(double tol) { + if (!(tol > 0)) { + throw util::IllegalArgumentException("Angle step tolerance must be positive"); + } + maxAngleRadians = tol * MATH_PI / 180.0; + } + +private: + double radiusTolerance{1e-6}; + double maxAngleRadians{45.01 * MATH_PI / 180.0}; + double maxExteriorAngleDifferenceRadians{0.01}; +}; + +} \ No newline at end of file diff --git a/include/geos/geom/CircularArc.h b/include/geos/geom/CircularArc.h index de75334fcc..5e97f37882 100644 --- a/include/geos/geom/CircularArc.h +++ b/include/geos/geom/CircularArc.h @@ -21,6 +21,11 @@ #include #include +// Forward declarations +namespace geos::algorithm { +class CurveToLineParams; +} + namespace geos { namespace geom { @@ -110,6 +115,10 @@ class GEOS_DLL CircularArc { /// Return the length of the arc double getLength() const; + /// Add linearized points representing this arc to the provided CoordinateSequence. + /// The origin point of the arc will NOT be added. + void addLinearizedPoints(CoordinateSequence& seq, const algorithm::CurveToLineParams& params) const; + /// Return the orientation of the arc as one of: /// - algorithm::Orientation::CLOCKWISE, /// - algorithm::Orientation::COUNTERCLOCKWISE @@ -142,6 +151,11 @@ class GEOS_DLL CircularArc { return algorithm::Distance::pointToSegment(midpoint, p0(), p2()); } + /// Interpolate Z/M values from the three points that define the arc. + /// Interpolation will be performed between [p0, p1] or [p1, p2] depending on + /// the position of the supplied point. + void interpolateZM(const CoordinateXY& pt, double& z, double& m) const; + bool isCCW() const { return getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE; } diff --git a/include/geos/geom/CircularString.h b/include/geos/geom/CircularString.h index a2f0e5afbf..e462e8af52 100644 --- a/include/geos/geom/CircularString.h +++ b/include/geos/geom/CircularString.h @@ -15,6 +15,7 @@ #pragma once #include +#include #include namespace geos { @@ -55,6 +56,8 @@ class GEOS_DLL CircularString : public SimpleCurve { return std::unique_ptr(reverseImpl()); } + std::unique_ptr getCurved(const algorithm::LineToCurveParams&) const; + protected: /// \brief @@ -76,6 +79,12 @@ class GEOS_DLL CircularString : public SimpleCurve { envelope = computeEnvelopeInternal(false); } + CircularString* getCurvedImpl(const algorithm::LineToCurveParams&) const override { + return cloneImpl(); + } + + LineString* getLinearizedImpl(const algorithm::CurveToLineParams&) const override; + int getSortIndex() const override { diff --git a/include/geos/geom/CompoundCurve.h b/include/geos/geom/CompoundCurve.h index cb9243d9e1..011550d557 100644 --- a/include/geos/geom/CompoundCurve.h +++ b/include/geos/geom/CompoundCurve.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include #include @@ -113,6 +114,10 @@ class GEOS_DLL CompoundCurve : public Curve { envelope = computeEnvelopeInternal(); } + LineString* getLinearizedImpl(const algorithm::CurveToLineParams&) const override; + + CompoundCurve* getCurvedImpl(const algorithm::LineToCurveParams&) const override { return cloneImpl(); } + int getSortIndex() const override { return SORTINDEX_COMPOUNDCURVE; diff --git a/include/geos/geom/Curve.h b/include/geos/geom/Curve.h index 8077609192..4c4f6445c4 100644 --- a/include/geos/geom/Curve.h +++ b/include/geos/geom/Curve.h @@ -16,6 +16,10 @@ #include +namespace geos::geom { + class LineString; +} + namespace geos { namespace geom { @@ -67,6 +71,10 @@ class GEOS_DLL Curve : public Geometry { /// or NULL if this is an EMPTY Curve. virtual std::unique_ptr getStartPoint() const = 0; + std::unique_ptr getLinearized(const algorithm::CurveToLineParams&) const; + + std::unique_ptr getCurved(const algorithm::LineToCurveParams&) const; + /// Returns true if the first and last coordinate in the Curve are the same virtual bool isClosed() const = 0; @@ -82,6 +90,7 @@ class GEOS_DLL Curve : public Geometry { protected: Curve(const GeometryFactory& factory) : Geometry(&factory) {} + Curve* getCurvedImpl(const algorithm::LineToCurveParams&) const override = 0; }; } diff --git a/include/geos/geom/CurvePolygon.h b/include/geos/geom/CurvePolygon.h index 1a12b48373..65ba7e66d1 100644 --- a/include/geos/geom/CurvePolygon.h +++ b/include/geos/geom/CurvePolygon.h @@ -15,6 +15,7 @@ #pragma once #include +#include namespace geos { namespace geom { @@ -35,6 +36,12 @@ class GEOS_DLL CurvePolygon : public SurfaceImpl { GeometryTypeId getGeometryTypeId() const override; + std::unique_ptr getCurved(const algorithm::LineToCurveParams&) { + return std::unique_ptr(cloneImpl()); + } + + std::unique_ptr getLinearized(const algorithm::CurveToLineParams&) const; + bool hasCurvedComponents() const override; void normalize() override; @@ -42,7 +49,11 @@ class GEOS_DLL CurvePolygon : public SurfaceImpl { protected: using SurfaceImpl::SurfaceImpl; - Geometry* cloneImpl() const override; + CurvePolygon* cloneImpl() const override; + + Polygon* getLinearizedImpl(const algorithm::CurveToLineParams&) const override; + + CurvePolygon* getCurvedImpl(const algorithm::LineToCurveParams&) const override { return cloneImpl(); } int getSortIndex() const override diff --git a/include/geos/geom/Geometry.h b/include/geos/geom/Geometry.h index dedf10022f..8fb4428327 100644 --- a/include/geos/geom/Geometry.h +++ b/include/geos/geom/Geometry.h @@ -50,6 +50,10 @@ // Forward declarations namespace geos { +namespace algorithm { +class CurveToLineParams; +class LineToCurveParams; +} namespace geom { class Coordinate; class CoordinateFilter; @@ -335,6 +339,20 @@ class GEOS_DLL Geometry { return this; } + /// Compute an approximation of the Geometry that contains no arcs. + /// If the Geometry already contains no arcs, a copy will be returned. + std::unique_ptr getLinearized(const algorithm::CurveToLineParams& params) const + { + return std::unique_ptr(getLinearizedImpl(params)); + } + + /// Attempt to replace linear sections of a geometry with arcs, where possible. + /// Any arcs that already exist in the Geometry will be retained unmodified. + std::unique_ptr getCurved(const algorithm::LineToCurveParams& params) const + { + return std::unique_ptr(getCurvedImpl(params)); + } + /** * \brief Tests the validity of this Geometry. * @@ -902,6 +920,10 @@ class GEOS_DLL Geometry { /// Make a geometry with coordinates in reverse order virtual Geometry* reverseImpl() const = 0; + virtual Geometry* getLinearizedImpl(const algorithm::CurveToLineParams&) const = 0; + + virtual Geometry* getCurvedImpl(const algorithm::LineToCurveParams&) const = 0; + /// Returns true if the array contains any non-empty Geometrys. template static bool hasNonEmptyElements(const std::vector* geometries) { diff --git a/include/geos/geom/GeometryCollection.h b/include/geos/geom/GeometryCollection.h index fd30f92046..1fd3826bbe 100644 --- a/include/geos/geom/GeometryCollection.h +++ b/include/geos/geom/GeometryCollection.h @@ -168,6 +168,14 @@ class GEOS_DLL GeometryCollection : public Geometry { /// Returns the number of geometries in this collection std::size_t getNumGeometries() const override; + std::unique_ptr getCurved(const algorithm::LineToCurveParams& params) { + return std::unique_ptr(getCurvedImpl(params)); + } + + std::unique_ptr getLinearized(const algorithm::CurveToLineParams& params) { + return std::unique_ptr(getLinearizedImpl(params)); + } + /// Returns a pointer to the nth Geometry in this collection const Geometry* getGeometryN(std::size_t n) const override; @@ -239,6 +247,10 @@ class GEOS_DLL GeometryCollection : public Geometry { GeometryCollection* reverseImpl() const override; + GeometryCollection* getLinearizedImpl(const algorithm::CurveToLineParams&) const override; + + GeometryCollection* getCurvedImpl(const algorithm::LineToCurveParams&) const override; + int getSortIndex() const override { diff --git a/include/geos/geom/LineString.h b/include/geos/geom/LineString.h index 25764c73dc..8ec488f501 100644 --- a/include/geos/geom/LineString.h +++ b/include/geos/geom/LineString.h @@ -92,6 +92,8 @@ class GEOS_DLL LineString: public SimpleCurve { double getLength() const override; + std::unique_ptr getCurved(const algorithm::LineToCurveParams&) const; + bool isCurved() const override { return false; } @@ -125,6 +127,10 @@ class GEOS_DLL LineString: public SimpleCurve { LineString(const std::shared_ptr & pts, const GeometryFactory& newFactory); + LineString* getLinearizedImpl(const algorithm::CurveToLineParams&) const override { return cloneImpl(); } + + Curve* getCurvedImpl(const algorithm::LineToCurveParams&) const override; + LineString* cloneImpl() const override { return new LineString(*this); } LineString* reverseImpl() const override; diff --git a/include/geos/geom/MultiCurve.h b/include/geos/geom/MultiCurve.h index 5505f5d967..ae67539b3d 100644 --- a/include/geos/geom/MultiCurve.h +++ b/include/geos/geom/MultiCurve.h @@ -50,6 +50,10 @@ class GEOS_DLL MultiCurve : public GeometryCollection { GeometryTypeId getGeometryTypeId() const override; + std::unique_ptr getLinearized(const algorithm::CurveToLineParams& params) const { + return std::unique_ptr(getLinearizedImpl(params)); + } + bool hasDimension(Dimension::DimensionType d) const override { return d == Dimension::L; @@ -114,6 +118,10 @@ class GEOS_DLL MultiCurve : public GeometryCollection { MultiCurve* reverseImpl() const override; + MultiCurve* getCurvedImpl(const algorithm::LineToCurveParams&) const override { return cloneImpl(); } + + MultiLineString* getLinearizedImpl(const algorithm::CurveToLineParams&) const override; + int getSortIndex() const override { diff --git a/include/geos/geom/MultiLineString.h b/include/geos/geom/MultiLineString.h index 2b6cc76fdf..b2a242f722 100644 --- a/include/geos/geom/MultiLineString.h +++ b/include/geos/geom/MultiLineString.h @@ -81,6 +81,10 @@ class GEOS_DLL MultiLineString: public GeometryCollection { GeometryTypeId getGeometryTypeId() const override; + std::unique_ptr getLinearized(const algorithm::CurveToLineParams&) const { + return clone(); + } + bool isClosed() const; std::unique_ptr clone() const @@ -132,6 +136,10 @@ class GEOS_DLL MultiLineString: public GeometryCollection { MultiLineString* reverseImpl() const override; + GeometryCollection* getCurvedImpl(const algorithm::LineToCurveParams&) const override; + + MultiLineString* getLinearizedImpl(const algorithm::CurveToLineParams&) const override; + int getSortIndex() const override { diff --git a/include/geos/geom/MultiPolygon.h b/include/geos/geom/MultiPolygon.h index f4cca3a384..049b4c6ebd 100644 --- a/include/geos/geom/MultiPolygon.h +++ b/include/geos/geom/MultiPolygon.h @@ -26,7 +26,6 @@ #include // for inheritance #include // for inheritance #include // for Dimension::DimensionType -#include // Forward declarations @@ -132,6 +131,10 @@ class GEOS_DLL MultiPolygon: public GeometryCollection { MultiPolygon* cloneImpl() const override { return new MultiPolygon(*this); } + GeometryCollection* getCurvedImpl(const algorithm::LineToCurveParams&) const override; + + MultiPolygon* getLinearizedImpl(const algorithm::CurveToLineParams&) const override { return cloneImpl(); } + MultiPolygon* reverseImpl() const override; int diff --git a/include/geos/geom/MultiSurface.h b/include/geos/geom/MultiSurface.h index 73871e4f0b..754345fd12 100644 --- a/include/geos/geom/MultiSurface.h +++ b/include/geos/geom/MultiSurface.h @@ -49,6 +49,10 @@ class GEOS_DLL MultiSurface : public GeometryCollection { GeometryTypeId getGeometryTypeId() const override; + std::unique_ptr getLinearized(const algorithm::CurveToLineParams& params) const { + return std::unique_ptr(getLinearizedImpl(params)); + } + bool hasDimension(Dimension::DimensionType d) const override { return d == Dimension::A; @@ -81,6 +85,10 @@ class GEOS_DLL MultiSurface : public GeometryCollection { return new MultiSurface(*this); } + MultiSurface* getCurvedImpl(const algorithm::LineToCurveParams&) const override { return cloneImpl(); } + + MultiPolygon* getLinearizedImpl(const algorithm::CurveToLineParams&) const override; + int getSortIndex() const override { diff --git a/include/geos/geom/Point.h b/include/geos/geom/Point.h index cc944d9721..0ccec2ac3f 100644 --- a/include/geos/geom/Point.h +++ b/include/geos/geom/Point.h @@ -187,6 +187,10 @@ class GEOS_DLL Point : public Geometry { Point(const Point& p); + Point* getCurvedImpl(const algorithm::LineToCurveParams&) const override { return cloneImpl(); }; + + Point* getLinearizedImpl(const algorithm::CurveToLineParams&) const override { return cloneImpl(); }; + Point* cloneImpl() const override { return new Point(*this); } Point* reverseImpl() const override { return new Point(*this); } diff --git a/include/geos/geom/Polygon.h b/include/geos/geom/Polygon.h index 98e4fe2620..99bcb242b5 100644 --- a/include/geos/geom/Polygon.h +++ b/include/geos/geom/Polygon.h @@ -94,6 +94,12 @@ class GEOS_DLL Polygon: public SurfaceImpl { std::string getGeometryType() const override; GeometryTypeId getGeometryTypeId() const override; + std::unique_ptr getCurved(const algorithm::LineToCurveParams& params) const { + return std::unique_ptr(getCurvedImpl(params)); + } + + std::unique_ptr getLinearized(const algorithm::CurveToLineParams&) const; + void normalize() override; std::unique_ptr reverse() const { return std::unique_ptr(reverseImpl()); } @@ -116,6 +122,10 @@ class GEOS_DLL Polygon: public SurfaceImpl { using SurfaceImpl::SurfaceImpl; + Surface* getCurvedImpl(const algorithm::LineToCurveParams&) const override; + + Polygon* getLinearizedImpl(const algorithm::CurveToLineParams&) const override { return cloneImpl(); }; + Polygon* cloneImpl() const override { return new Polygon(*this); } Polygon* reverseImpl() const override; diff --git a/src/algorithm/CircularArcIntersector.cpp b/src/algorithm/CircularArcIntersector.cpp index 1b3bedcaf5..b6f988dac3 100644 --- a/src/algorithm/CircularArcIntersector.cpp +++ b/src/algorithm/CircularArcIntersector.cpp @@ -25,109 +25,6 @@ using geos::geom::CircularArc; namespace geos::algorithm { -static double -interpolateValue(double a1, double a2, double frac) -{ - frac = std::clamp(frac, 0.0, 1.0); - if (std::isnan(a1)) { - return a2; - } - if (std::isnan(a2)) { - return a1; - } - return a1 + frac * (a2 - a1); -} - -static void -interpolateZM(const CircularArc& arc, const CoordinateXY& pt, double& z, double& m) -{ - using geom::Ordinate; - - const CoordinateSequence& seq = *arc.getCoordinateSequence(); - std::size_t i0 = arc.getCoordinatePosition(); - - // Read Z, M from control point - double z1, m1; - seq.applyAt(i0 + 1, [&z1, &m1](const auto& arcPt) { - z1 = arcPt.template get(); - m1 = arcPt.template get(); - }); - // Test point = control point? - // Take Z, M from the control point - if (arc.p1().equals2D(pt)) { - z = z1; - m = m1; - return; - } - - // Read Z, M from start point - double z0, m0; - seq.applyAt(i0, [&z0, &m0](const auto& arcPt) { - z0 = arcPt.template get(); - m0 = arcPt.template get(); - }); - // Test point = start point? - // Take Z, M from the start point - if (arc.p0().equals2D(pt)) { - z = z0; - m = m0; - return; - } - - // Read Z, M from end point - double z2, m2; - seq.applyAt(i0 + 2, [&z2, &m2](const auto& arcPt) { - z2 = arcPt.template get(); - m2 = arcPt.template get(); - }); - // Test point = end point? - // Take Z, M from the end point - if (arc.p2().equals2D(pt)) { - z = z2; - m = m2; - return; - } - - double theta0 = arc.theta0(); - const double theta1 = arc.theta1(); - double theta2 = arc.theta2(); - const double theta = CircularArcs::getAngle(pt, arc.getCenter()); - - if (!arc.isCCW()) { - std::swap(theta0, theta2); - std::swap(z0, z2); - std::swap(m0, m2); - } - - if (std::isnan(z1)) { - // Interpolate between p0 / p2 - const double frac = Angle::fractionCCW(theta, theta0, theta2); - z = interpolateValue(z0, z2, frac); - } else if (Angle::isWithinCCW(theta, theta0, theta1)) { - // Interpolate between p0 / p1 - const double frac = Angle::fractionCCW(theta, theta0, theta1); - z = interpolateValue(z0, z1, frac); - } else { - // Interpolate between p1 / p2 - const double frac = Angle::fractionCCW(theta, theta1, theta2); - z = interpolateValue(z1, z2, frac); - } - - if (std::isnan(m1)) { - // Interpolate between p0 / p2 - const double frac = Angle::fractionCCW(theta, theta0, theta2); - m = interpolateValue(m0, m2, frac); - } else if (Angle::isWithinCCW(theta, theta0, theta1)) { - // Interpolate between p0 / p1 - const double frac = Angle::fractionCCW(theta, theta0, theta1); - m = interpolateValue(m0, m1, frac); - } else { - // Interpolate between p1 / p2 - const double frac = Angle::fractionCCW(theta, theta1, theta2); - m = interpolateValue(m1, m2, frac); - } - -} // Interpolate the Z/M values of a point lying on the provided line segment static void @@ -155,8 +52,8 @@ interpolateZM(const CircularArc& arc0, const CircularArc& arc1, geom::Coordinate double z0, m0; double z1, m1; - interpolateZM(arc0, pt, z0, m0); - interpolateZM(arc1, pt, z1, m1); + arc0.interpolateZM(pt, z0, m0); + arc1.interpolateZM(pt, z1, m1); if (std::isnan(pt.z)) { pt.z = Interpolate::getOrAverage(z0, z1); @@ -179,7 +76,7 @@ interpolateZM(const CircularArc& arc0, double z0, m0; double z1, m1; - interpolateZM(arc0, pt, z0, m0); + arc0.interpolateZM(pt, z0, m0); interpolateSegmentZM(seq, ind0, ind1, pt, z1, m1); if (std::isnan(pt.z)) { diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp index 55d7a433a4..8a9c1a844d 100644 --- a/src/algorithm/CircularArcs.cpp +++ b/src/algorithm/CircularArcs.cpp @@ -46,6 +46,11 @@ CircularArcs::getMidpointAngle(double theta0, double theta2, bool isCCW) return getMidpointAngle(theta2, theta0, true); } + if (theta0 == theta2) { + // full circle + return theta0 + MATH_PI; + } + double mid = (theta0 + theta2) / 2; if (!Angle::isWithinCCW(mid, theta0, theta2)) { mid += MATH_PI; diff --git a/src/algorithm/CurveBuilder.cpp b/src/algorithm/CurveBuilder.cpp new file mode 100644 index 0000000000..ef3215fc1a --- /dev/null +++ b/src/algorithm/CurveBuilder.cpp @@ -0,0 +1,310 @@ +/********************************************************************** +* + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2025-2026 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#include "geos/algorithm/LineToCurveParams.h" + +namespace geos::algorithm { + +using geom::CircularArc; +using geom::CoordinateSequence; +using geom::CoordinateXY; +using geom::CoordinateXYZM; +using geom::GeometryFactory; +using geom::LineString; + +CurveBuilder::CurveBuilder(const GeometryFactory &p_factory) + : factory(p_factory) {} + +std::unique_ptr +CurveBuilder::getCurved(const LineString &ls, const LineToCurveParams& params) { + CurveBuilder cb(*ls.getFactory()); + return cb.compute(ls, params); +} + +std::unique_ptr +CurveBuilder::compute(const LineString& ls, const LineToCurveParams& params) { + if (ls.isEmpty()) { + return ls.clone(); + } + + const auto& points = *ls.getSharedCoordinates(); + + std::size_t start = 0; + + while (start + 1 < points.getSize()) { + + if (start + 2 >= points.getSize()) { + addLineCoords(points, start, start + 1); + break; + } + + CircularArc arc(points, start); + + if (arc.isLinear()) { + addLineCoords(points, start, start + 2); + start += 2; + continue; + } + + if (arc.getAngle() > 2 *params.getMaxAngleRadians()) { + addLineCoords(points, start, start + 1); + start++; + continue; + } + + std::size_t stop = start + 3; + bool foundArc = false; + + // Continue to consume points until we are no longer on the same arc. + // We need to find at least one additional point that fits on this arc to consider this an arc. + while (stop < points.getSize()) { + const CoordinateXY& pt = points.getAt(stop); + + const double distance = pt.distance(arc.getCenter()); + + // Does the radius match? + if (std::abs(distance - arc.getRadius()) > params.getRadiusTolerance() * arc.getRadius()) { + break; + } + + const CoordinateXY& prev1 = points.getAt(stop - 1); + const CoordinateXY& prev2 = points.getAt(stop - 2); + + // Check that angle p[stop-1] /_ center /_ p[stop] is less than the step tolerance. + // This check is not done in PostGIS. + const double currAngle = std::abs(Angle::angleBetweenOriented(prev1, arc.getCenter(), pt)); + + if (currAngle > params.getMaxAngleRadians()) { + break; + } + + // Check that the angle p[stop-2] /_ p[stop-1] /_ p[stop] is consistent with that in the + // original section of the arc. + const double prevExtAngle = Angle::angleBetween(arc.p0(), arc.p1(), arc.p2()); + const double currExtAngle = Angle::angleBetween(prev2, prev1, pt); + + if (std::abs(currExtAngle - prevExtAngle) > params.getMaxExteriorAngleDifferenceRadians()) { + break; + } + + foundArc = true; + stop++; + } + + if (foundArc) { + addArc(arc, stop - 1); + start = stop - 1; + } else { + addLineCoords(points, start, start + 1); + start++; + } + + } + + finishArc(); + finishLine(); + + if (curves.size() == 1) { + return std::move(curves.front()); + } + + return factory.createCompoundCurve(std::move(curves)); +} + +void +CurveBuilder::addLineCoords(const CoordinateSequence& points, std::size_t from, std::size_t to) +{ + finishArc(); + + if (lineCoords) { + lineCoords->add(points, from + 1, to); + } else { + lineCoords = std::make_shared(0, points.hasZ(), points.hasM()); + lineCoords->add(points, from, to); + } +} + +void +CurveBuilder::finishArc() +{ + if (arcCoords) { + curves.push_back(factory.createCircularString(arcCoords)); + arcCoords.reset(); + } +} + +void +CurveBuilder::finishLine() { + if (lineCoords) { + curves.push_back(factory.createLineString(lineCoords)); + lineCoords.reset(); + } +} + +/// Interpolates Z and M values from the midpoint of the arc, by: +/// 1) Determining the two vertices of the linearized arc that bound the original arc midpoint +/// 2) Assuming that Z and M increase/decrease at a constant rate from the origin of the original +/// arc to its midpoint, and increase/decrease at a possibly different constant rate from the +/// midpoint of the original arc to its endpoint. +/// 3) Computing values of Z and M based on (2). +static void +interpolateMidpointZM(CoordinateXYZM& p1, const CircularArc& arc, size_t stop, const CoordinateXY& center) +{ + const auto start = arc.getCoordinatePosition(); + const auto nPoints = stop - start + 1; + const CoordinateSequence& points = *arc.getCoordinateSequence(); + + // We have an even number of vertices. Calculate Z/M of the control + // point in the original arc. + CoordinateXYZM a, b; + points.getAt(start + nPoints / 2 - 1, a); + points.getAt(start + nPoints / 2, b); + + const double thetaA = CircularArcs::getAngle(a, center); + const double theta0 = arc.theta0(); + const double theta1 = CircularArcs::getAngle(p1, center); + + // Assumption: point a has the same angle fraction over [p0, p1] that b has over [p2, p1] + const double f = arc.isCCW() ? Angle::fractionCCW(thetaA, theta0, theta1) : 1 - Angle::fractionCCW(thetaA, theta1, theta0); + + const double z0 = points.getZ(start); + const double z2 = points.getZ(stop); + + const double m0 = points.getM(start); + const double m2 = points.getM(stop); + + p1.z = (a.z + b.z - z0*(1 - f) - z2*(1 - f)) / (2 * f); + p1.m = (a.m + b.m - m0*(1 - f) - m2*(1 - f)) / (2 * f); +} + +/// Assigns Z and M values to the midpoint of an arc given points representing a linearized version of the arc. +/// If the linearized version of the arc has an odd number of points, the Z and M values are taken directly from +/// the central vertex of the linearized arc. If the linearized version of the arc has an even number of points, +/// the Z and M values are calculated using the two vertices that bound the midpoint of the arc. +static void +getOrInterpolateMidPointZM(CoordinateXYZM& p1, const CircularArc& arc, size_t stop, const CoordinateXY& center) { + const auto start = arc.getCoordinatePosition(); + const auto nPoints = stop - start + 1; + const CoordinateSequence& points = *arc.getCoordinateSequence(); + + if (nPoints % 2) { + // We have an odd number of vertices, so the central vertex should be the same + // as the control point in the original arc. + auto midpointIndex = start + nPoints / 2; + p1.z = points.getZ(midpointIndex); + p1.m = points.getM(midpointIndex); + } else { + interpolateMidpointZM(p1, arc, stop, center); + } +} + +void +CurveBuilder::addArc(const CircularArc& arc, std::size_t stop) { + finishLine(); + + const CoordinateSequence& points = *arc.getCoordinateSequence(); + std::size_t start = arc.getCoordinatePosition(); + + double xSum = 0.0; + double ySum = 0.0; + std::size_t nArcApproximations = 0; + + for (std::size_t i = start; i <= stop - 2; i++) { + CircularArc a(points, i); + const CoordinateXY& center = a.getCenter(); + + xSum += center.x; + ySum += center.y; + nArcApproximations++; + } + + const CoordinateXY averageCenter = { xSum / static_cast(nArcApproximations), ySum / static_cast(nArcApproximations) }; + + CoordinateXYZM p0, p2; + points.getAt(start, p0); + points.getAt(stop, p2); + + const double averageRadius = 0.5*(p0.distance(averageCenter) + p2.distance(averageCenter)); + + if (p0.equals2D(p2)) { + // Arc forms a complete circle + const bool isCCW = CircularArc(points, start).isCCW(); + + CoordinateXYZM p1(CircularArcs::getMidpoint(p0, p0, averageCenter, averageRadius, isCCW)); + + CoordinateXYZM p01(CircularArcs::getMidpoint(p0, p1, averageCenter, averageRadius, isCCW)); + CoordinateXYZM p12(CircularArcs::getMidpoint(p1, p2, averageCenter, averageRadius, isCCW)); + + if (!arcCoords) { + // should always be the case + arcCoords = std::make_shared(0, points.hasZ(), points.hasM()); + arcCoords->reserve(5); + arcCoords->add(p0); + } + + if (points.hasZ() || points.hasM()) { + auto nPoints = stop - start + 1; + if ((nPoints - 1) % 4 == 0) { + auto i01 = start + (nPoints - 1) / 4; + p01.z = points.getZ(i01); + p01.m = points.getM(i01); + + auto i1 = start + (nPoints - 1) / 2; + p1.z = points.getZ(i1); + p1.m = points.getM(i1); + + auto i12 = start + 3 * (nPoints - 1) / 4; + p12.z = points.getZ(i12); + p12.m = points.getM(i12); + } else { + getOrInterpolateMidPointZM(p1, arc, stop, averageCenter); + p01.z = 0.5*(p0.z + p1.z); + p01.m = 0.5*(p0.m + p1.m); + p12.z = 0.5*(p1.z + p2.z); + p12.m = 0.5*(p1.m + p2.m); + } + } + + arcCoords->add(p01); + arcCoords->add(p1); + arcCoords->add(p12); + arcCoords->add(p2); + } else { + CoordinateXYZM p1(CircularArcs::getMidpoint(p0, p2, averageCenter, averageRadius, arc.isCCW())); + + if (points.hasZ() || points.hasM()) { + getOrInterpolateMidPointZM(p1, arc, stop, averageCenter); + } + + if (!arcCoords) { + arcCoords = std::make_shared(0, points.hasZ(), points.hasM()); + arcCoords->reserve(3); + arcCoords->add(p0); + } + + arcCoords->add(p1); + arcCoords->add(p2); + } +} + +} diff --git a/src/coverage/CoverageCleaner.cpp b/src/coverage/CoverageCleaner.cpp index 51c822e056..8df6aacf2c 100644 --- a/src/coverage/CoverageCleaner.cpp +++ b/src/coverage/CoverageCleaner.cpp @@ -106,7 +106,11 @@ CoverageCleaner::CoverageCleaner(std::vector& p_coverage) : coverage(p_coverage) , geomFactory(p_coverage.empty() ? nullptr : coverage[0]->getFactory()) , snappingDistance(computeDefaultSnappingDistance(p_coverage)) -{} +{ + for (const auto& geom : coverage) { + util::ensureNoCurvedComponents(geom); + } +} /* public */ diff --git a/src/geom/CircularArc.cpp b/src/geom/CircularArc.cpp index 1ec27bbe67..535dcfb635 100644 --- a/src/geom/CircularArc.cpp +++ b/src/geom/CircularArc.cpp @@ -12,10 +12,13 @@ * **********************************************************************/ +#include #include #include #include +#include "geos/algorithm/CurveToLineParams.h" + namespace geos::geom { CircularArc::CircularArc() : @@ -292,6 +295,154 @@ CircularArc::getLength() const { return getAngle()*getRadius(); } + +void +CircularArc::addLinearizedPoints(CoordinateSequence& seq, const algorithm::CurveToLineParams& params) const +{ + if (isLinear()) { + seq.add(*getCoordinateSequence(), getCoordinatePosition() + 1, getCoordinatePosition() + 2); + return; + } + + const double stepDegrees = params.getStepSizeDegrees(*this); + + const double angle = getAngle(); + const bool isCCW = getOrientation() == geos::algorithm::Orientation::COUNTERCLOCKWISE; + const double stepRad = stepDegrees * MATH_PI / 180.0; + const int nSegments = std::max(static_cast(std::ceil(angle / stepRad)), 2); + + double adjStepRad = angle / nSegments; + + const CoordinateXY& center = getCenter(); + const double radius = getRadius(); + + // To ensure that the vertices in the linearized arc are independent of the + // arc orientation, we process the arc in a CCW manner regardless of its + // original orientation. + const double startAngle = isCCW ? theta0() : theta2(); + + const bool hasZ = getCoordinateSequence()->hasZ(); + const bool hasM = getCoordinateSequence()->hasM(); + + for (int i = 1; i < nSegments; i++) { + const int j = isCCW ? i: nSegments - i; + const double theta = startAngle + j*adjStepRad; + + CoordinateXYZM pt{geos::algorithm::CircularArcs::createPoint(center, radius, theta)}; + + if (hasZ || hasM) { + interpolateZM(pt, pt.z, pt.m); + } + + seq.add(pt); + } + + seq.add(*getCoordinateSequence(), getCoordinatePosition() + 2, getCoordinatePosition() + 2); +} + +static double +interpolateValue(double a1, double a2, double frac) +{ + frac = std::clamp(frac, 0.0, 1.0); + if (std::isnan(a1)) { + return a2; + } + if (std::isnan(a2)) { + return a1; + } + return a1 + frac * (a2 - a1); +} + +void +CircularArc::interpolateZM(const CoordinateXY &pt, double &z, double &m) const +{ + using geom::Ordinate; + + const CoordinateSequence& seq = *getCoordinateSequence(); + std::size_t i0 = getCoordinatePosition(); + + // Read Z, M from control point + double z1, m1; + seq.applyAt(i0 + 1, [&z1, &m1](const auto& arcPt) { + z1 = arcPt.template get(); + m1 = arcPt.template get(); + }); + // Test point = control point? + // Take Z, M from the control point + if (p1().equals2D(pt)) { + z = z1; + m = m1; + return; + } + + // Read Z, M from start point + double z0, m0; + seq.applyAt(i0, [&z0, &m0](const auto& arcPt) { + z0 = arcPt.template get(); + m0 = arcPt.template get(); + }); + // Test point = start point? + // Take Z, M from the start point + if (p0().equals2D(pt)) { + z = z0; + m = m0; + return; + } + + // Read Z, M from end point + double z2, m2; + seq.applyAt(i0 + 2, [&z2, &m2](const auto& arcPt) { + z2 = arcPt.template get(); + m2 = arcPt.template get(); + }); + // Test point = end point? + // Take Z, M from the end point + if (p2().equals2D(pt)) { + z = z2; + m = m2; + return; + } + + double norm_theta0 = theta0(); + const double norm_theta1 = theta1(); + double norm_theta2 = theta2(); + const double theta = algorithm::CircularArcs::getAngle(pt, getCenter()); + + if (!isCCW()) { + std::swap(norm_theta0, norm_theta2); + std::swap(z0, z2); + std::swap(m0, m2); + } + + if (std::isnan(z1)) { + // Interpolate between p0 / p2 + const double frac = algorithm::Angle::fractionCCW(theta, norm_theta0, norm_theta2); + z = interpolateValue(z0, z2, frac); + } else if (algorithm::Angle::isWithinCCW(theta, norm_theta0, norm_theta1)) { + // Interpolate between p0 / p1 + const double frac = algorithm::Angle::fractionCCW(theta, norm_theta0, norm_theta1); + z = interpolateValue(z0, z1, frac); + } else { + // Interpolate between p1 / p2 + const double frac = algorithm::Angle::fractionCCW(theta, norm_theta1, norm_theta2); + z = interpolateValue(z1, z2, frac); + } + + if (std::isnan(m1)) { + // Interpolate between p0 / p2 + const double frac = algorithm::Angle::fractionCCW(theta, norm_theta0, norm_theta2); + m = interpolateValue(m0, m2, frac); + } else if (algorithm::Angle::isWithinCCW(theta, norm_theta0, norm_theta1)) { + // Interpolate between p0 / p1 + const double frac = algorithm::Angle::fractionCCW(theta, norm_theta0, norm_theta1); + m = interpolateValue(m0, m1, frac); + } else { + // Interpolate between p1 / p2 + const double frac = algorithm::Angle::fractionCCW(theta, norm_theta1, norm_theta2); + m = interpolateValue(m1, m2, frac); + } +} + bool CircularArc::isUpwardAtPoint(const CoordinateXY& q) const { auto quad = geom::Quadrant::quadrant(getCenter(), q); @@ -409,4 +560,5 @@ CircularArc::toString() const { return ss.str(); } + } diff --git a/src/geom/CircularString.cpp b/src/geom/CircularString.cpp index fe6b79cd59..4eeb941dea 100644 --- a/src/geom/CircularString.cpp +++ b/src/geom/CircularString.cpp @@ -116,8 +116,7 @@ CircularString::normalize() /*private*/ void -CircularString::normalizeClosed() -{ +CircularString::normalizeClosed() { if (isEmpty()) { return; } @@ -154,6 +153,30 @@ CircularString::normalizeClosed() } } +std::unique_ptr +CircularString::getCurved(const algorithm::LineToCurveParams&) const +{ + return getFactory()->createCircularString(points); +} + + +LineString* +CircularString::getLinearizedImpl(const algorithm::CurveToLineParams& params) const { + if (isEmpty()) { + return getFactory()->createLineString().release(); + } + + auto seq = std::make_shared(0, hasZ(), hasM()); + seq->add(*getCoordinatesRO(), static_cast(0), 0); + + for (const CircularArc& arc : getArcs()) + { + arc.addLinearizedPoints(*seq, params); + } + + return getFactory()->createLineString(seq).release(); +} + CircularString* CircularString::reverseImpl() const { diff --git a/src/geom/CompoundCurve.cpp b/src/geom/CompoundCurve.cpp index 70916aa9c4..609c4f8b87 100644 --- a/src/geom/CompoundCurve.cpp +++ b/src/geom/CompoundCurve.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include + namespace geos { namespace geom { @@ -239,6 +241,30 @@ CompoundCurve::getLength() const return sum; } +LineString* +CompoundCurve::getLinearizedImpl(const algorithm::CurveToLineParams& params) const +{ + auto seq = std::make_shared(0, hasZ(), hasM()); + for (const auto& curve : curves) { + const CoordinateSequence* curveSeq = curve->getCoordinatesRO(); + + if (seq->isEmpty()) { + seq->add(*curveSeq, static_cast(0), 0); + } + + if (curve->isCurved()) { + for (std::size_t i = 0; i < curveSeq->size() - 2; i += 2) + { + CircularArc arc(*curveSeq, i); + arc.addLinearizedPoints(*seq, params); + } + } else { + seq->add(*curveSeq, 1, curveSeq->size() - 1); + } + } + return getFactory()->createLineString(std::move(seq)).release(); +} + std::size_t CompoundCurve::getNumCurves() const { diff --git a/src/geom/Curve.cpp b/src/geom/Curve.cpp index a556782672..1b12c288de 100644 --- a/src/geom/Curve.cpp +++ b/src/geom/Curve.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include namespace geos { namespace geom { @@ -55,11 +57,22 @@ Curve::isRing() const } std::unique_ptr -Curve::reverse() const -{ +Curve::reverse() const { return std::unique_ptr(static_cast(Geometry::reverse().release())); } +std::unique_ptr +Curve::getLinearized(const algorithm::CurveToLineParams& params) const +{ + return std::unique_ptr(detail::down_cast(getLinearizedImpl(params))); +} + +std::unique_ptr +Curve::getCurved(const algorithm::LineToCurveParams& params) const +{ + return std::unique_ptr(getCurvedImpl(params)); +} + } } diff --git a/src/geom/CurvePolygon.cpp b/src/geom/CurvePolygon.cpp index 7f07e9f41c..4986f5622c 100644 --- a/src/geom/CurvePolygon.cpp +++ b/src/geom/CurvePolygon.cpp @@ -22,6 +22,8 @@ #include #include +#include "geos/algorithm/CurveToLineParams.h" + namespace geos { namespace geom { @@ -101,6 +103,28 @@ namespace geom { return sum; } + std::unique_ptr + CurvePolygon::getLinearized(const algorithm::CurveToLineParams& params) const { + return std::unique_ptr(getLinearizedImpl(params)); + } + + Polygon* + CurvePolygon::getLinearizedImpl(const algorithm::CurveToLineParams& params) const { + const auto& gfact = *getFactory(); + + auto linShell = gfact.createLinearRing(shell->getLinearized(params)->getSharedCoordinates()); + + if (holes.empty()) { + return getFactory()->createPolygon(std::move(linShell)).release(); + } + + std::vector> linHoles(holes.size()); + for (size_t i = 0; i < holes.size(); i++) { + linHoles[i] = gfact.createLinearRing(holes[i]->getLinearized(params)->getSharedCoordinates()); + } + return getFactory()->createPolygon(std::move(linShell), std::move(linHoles)).release(); + } + bool CurvePolygon::hasCurvedComponents() const { if (shell->hasCurvedComponents()) { return true; @@ -113,7 +137,7 @@ namespace geom { return false; } - Geometry* + CurvePolygon* CurvePolygon::cloneImpl() const { return new CurvePolygon(*this); } diff --git a/src/geom/GeometryCollection.cpp b/src/geom/GeometryCollection.cpp index 25877b81a6..8071260fe3 100644 --- a/src/geom/GeometryCollection.cpp +++ b/src/geom/GeometryCollection.cpp @@ -31,6 +31,8 @@ #include #include +#include "geos/algorithm/CurveToLineParams.h" + namespace geos { namespace geom { // geos::geom @@ -107,6 +109,30 @@ GeometryCollection::getCoordinates() const return coordinates; } +GeometryCollection* +GeometryCollection::getCurvedImpl(const algorithm::LineToCurveParams& params) const +{ + std::vector> curvedGeoms(geometries.size()); + for (std::size_t i = 0; i < geometries.size(); i++) { + curvedGeoms[i] = geometries[i]->getCurved(params); + } + + return getFactory()->createGeometryCollection(std::move(curvedGeoms)).release(); +} + + +GeometryCollection* +GeometryCollection::getLinearizedImpl(const algorithm::CurveToLineParams& params) const +{ + std::vector> linGeoms(geometries.size()); + for (std::size_t i = 0; i < geometries.size(); i++) { + linGeoms[i] = geometries[i]->getLinearized(params); + } + + return getFactory()->createGeometryCollection(std::move(linGeoms)).release(); +} + + bool GeometryCollection::isEmpty() const { diff --git a/src/geom/LineString.cpp b/src/geom/LineString.cpp index c05932754c..ee04e0f3b9 100644 --- a/src/geom/LineString.cpp +++ b/src/geom/LineString.cpp @@ -19,8 +19,13 @@ **********************************************************************/ #include +#include +#include #include #include +#include +#include +#include #include #include #include @@ -101,6 +106,17 @@ LineString::validateConstruction() } } +Curve* +LineString::getCurvedImpl(const algorithm::LineToCurveParams& params) const +{ + return getCurved(params).release(); +} + +std::unique_ptr +LineString::getCurved(const algorithm::LineToCurveParams& params) const +{ + return CurveBuilder::getCurved(*this, params); +} std::string LineString::getGeometryType() const diff --git a/src/geom/MultiCurve.cpp b/src/geom/MultiCurve.cpp index 31e13eb2c4..136aa9e5ee 100644 --- a/src/geom/MultiCurve.cpp +++ b/src/geom/MultiCurve.cpp @@ -78,6 +78,18 @@ MultiCurve::getGeometryTypeId() const return GEOS_MULTICURVE; } +MultiLineString* +MultiCurve::getLinearizedImpl(const algorithm::CurveToLineParams& params) const +{ + std::vector> lines(geometries.size()); + + for (std::size_t i = 0; i < geometries.size(); i++) { + lines[i] = geometries[i]->getLinearized(params); + } + + return getFactory()->createMultiLineString(std::move(lines)).release(); +} + bool MultiCurve::isClosed() const { diff --git a/src/geom/MultiLineString.cpp b/src/geom/MultiLineString.cpp index 00b52dfa53..79ef7b9e89 100644 --- a/src/geom/MultiLineString.cpp +++ b/src/geom/MultiLineString.cpp @@ -20,12 +20,14 @@ #include #include +#include #include #include #include #include -#include + +#include "geos/algorithm/LineToCurveParams.h" using geos::geom::GeometryFactory; using geos::geom::MultiLineString; @@ -94,6 +96,31 @@ MultiLineString::getGeometryTypeId() const return GEOS_MULTILINESTRING; } +GeometryCollection* +MultiLineString::getCurvedImpl(const algorithm::LineToCurveParams& params) const +{ + std::vector> curvedGeoms(geometries.size()); + + bool hasCurves = false; + + for (std::size_t i = 0; i < geometries.size(); i++) { + curvedGeoms[i] = detail::down_cast(geometries[i].get())->getCurved(params); + hasCurves |= curvedGeoms[i]->hasCurvedComponents(); + } + + if (hasCurves) { + return getFactory()->createMultiCurve(std::move(curvedGeoms)).release(); + } + + return getFactory()->createMultiLineString(std::move(curvedGeoms)).release(); +} + +MultiLineString* +MultiLineString::getLinearizedImpl(const algorithm::CurveToLineParams&) const +{ + return cloneImpl(); +} + MultiLineString* MultiLineString::reverseImpl() const { diff --git a/src/geom/MultiPolygon.cpp b/src/geom/MultiPolygon.cpp index fa7cbaf713..ae5c253fcd 100644 --- a/src/geom/MultiPolygon.cpp +++ b/src/geom/MultiPolygon.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -87,6 +88,26 @@ MultiPolygon::getBoundary() const return getFactory()->createMultiLineString(std::move(allRings)); } +GeometryCollection* +MultiPolygon::getCurvedImpl(const algorithm::LineToCurveParams& params) const { + std::vector> curvedGeoms(geometries.size()); + + bool hasCurves = false; + + for (std::size_t i = 0; i < geometries.size(); i++) { + curvedGeoms[i] = geometries[i]->getCurved(params); + if (curvedGeoms[i]->hasCurvedComponents()) { + hasCurves = true; + } + } + + if (hasCurves) { + return getFactory()->createMultiSurface(std::move(curvedGeoms)).release(); + } + + return getFactory()->createMultiPolygon(std::move(curvedGeoms)).release(); +} + GeometryTypeId MultiPolygon::getGeometryTypeId() const { diff --git a/src/geom/MultiSurface.cpp b/src/geom/MultiSurface.cpp index b8e299f6a6..1c1dc15705 100644 --- a/src/geom/MultiSurface.cpp +++ b/src/geom/MultiSurface.cpp @@ -18,6 +18,8 @@ #include #include +#include "geos/algorithm/CurveToLineParams.h" + namespace geos { namespace geom { @@ -86,6 +88,18 @@ MultiSurface::getGeometryTypeId() const return GEOS_MULTISURFACE; } +MultiPolygon* +MultiSurface::getLinearizedImpl(const algorithm::CurveToLineParams& params) const +{ + std::vector> polygons(geometries.size()); + + for (std::size_t i = 0; i < geometries.size(); i++) { + polygons[i] = geometries[i]->getLinearized(params); + } + + return getFactory()->createMultiPolygon(std::move(polygons)).release(); +} + MultiSurface* MultiSurface::reverseImpl() const { diff --git a/src/geom/Polygon.cpp b/src/geom/Polygon.cpp index 116374ca95..806b3503fd 100644 --- a/src/geom/Polygon.cpp +++ b/src/geom/Polygon.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include // for getBoundary() @@ -36,6 +37,8 @@ #include #include +#include "geos/algorithm/CurveToLineParams.h" + #ifndef GEOS_DEBUG #define GEOS_DEBUG 0 #endif @@ -66,6 +69,39 @@ Polygon::getCoordinates() const return cl; } +Surface* +Polygon::getCurvedImpl(const algorithm::LineToCurveParams& params) const +{ + auto curvedShell = shell->getCurved(params); + bool isCurved = curvedShell->hasCurvedComponents(); + + if (holes.empty()) { + if (isCurved) { + return getFactory()->createCurvePolygon(std::move(curvedShell)).release(); + } else { + return cloneImpl(); + } + } + + std::vector> holesCurved(holes.size()); + for (std::size_t i = 0; i < holes.size(); i++) { + holesCurved[i] = holes[i]->getCurved(params); + isCurved |= holesCurved[i]->hasCurvedComponents(); + } + + if (isCurved) { + return getFactory()->createCurvePolygon(std::move(curvedShell), std::move(holesCurved)).release(); + } + + return cloneImpl(); +} + +std::unique_ptr +Polygon::getLinearized(const algorithm::CurveToLineParams& params) const +{ + return std::unique_ptr(getLinearizedImpl(params)); +} + std::string Polygon::getGeometryType() const { diff --git a/src/triangulate/DelaunayTriangulationBuilder.cpp b/src/triangulate/DelaunayTriangulationBuilder.cpp index cd80ecdcd7..424de2a131 100644 --- a/src/triangulate/DelaunayTriangulationBuilder.cpp +++ b/src/triangulate/DelaunayTriangulationBuilder.cpp @@ -77,8 +77,6 @@ DelaunayTriangulationBuilder::DelaunayTriangulationBuilder() : void DelaunayTriangulationBuilder::setSites(const Geometry& geom) { - util::ensureNoCurvedComponents(geom); - // remove any duplicate points (they will cause the triangulation to fail) siteCoords = extractUniqueCoordinates(geom); } diff --git a/src/triangulate/VoronoiDiagramBuilder.cpp b/src/triangulate/VoronoiDiagramBuilder.cpp index eeae7f0643..d4b09c8c5d 100644 --- a/src/triangulate/VoronoiDiagramBuilder.cpp +++ b/src/triangulate/VoronoiDiagramBuilder.cpp @@ -51,7 +51,6 @@ VoronoiDiagramBuilder::VoronoiDiagramBuilder() : void VoronoiDiagramBuilder::setSites(const geom::Geometry& geom) { - util::ensureNoCurvedComponents(geom); siteCoords = DelaunayTriangulationBuilder::extractUniqueCoordinates(geom); inputGeom = &geom; } diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp index ccb3e49795..031186523c 100644 --- a/tests/unit/algorithm/CircularArcsTest.cpp +++ b/tests/unit/algorithm/CircularArcsTest.cpp @@ -305,5 +305,19 @@ void object::test<17>() { CoordinateXY{std::sqrt(2)/2, std::sqrt(2)/2}.distance(CoordinateXY{0.5, 0.5})); } +template<> +template<> +void object::test<18>() { + set_test_name("getMidpointAngle"); + + // half-circle + ensure_equals(CircularArcs::getMidpointAngle(0, MATH_PI, true), MATH_PI/2); + ensure_equals(CircularArcs::getMidpointAngle(0, MATH_PI, false), 3*MATH_PI/2); + + // full circle + ensure_equals(CircularArcs::getMidpointAngle(0, 0, true), MATH_PI); + ensure_equals(CircularArcs::getMidpointAngle(0, 0, false), MATH_PI); +} + } diff --git a/tests/unit/algorithm/CurveBuilderTest.cpp b/tests/unit/algorithm/CurveBuilderTest.cpp new file mode 100644 index 0000000000..e893b96b23 --- /dev/null +++ b/tests/unit/algorithm/CurveBuilderTest.cpp @@ -0,0 +1,310 @@ +#include + +#include +#include +#include + +#include "utility.h" + +using geos::algorithm::CurveToLineParams; +using geos::algorithm::LineToCurveParams; + +namespace tut { + +struct test_curvebuilder_data { + geos::io::WKTReader reader_; + + void checkRoundTrip(const std::string& wkt_in, const std::string& wkt_expected, double stepSizeDegrees) const { + auto inCurve = reader_.read(wkt_in); + + auto ctlParams = CurveToLineParams::stepSizeDegrees(stepSizeDegrees); + auto linearized = inCurve->getLinearized(ctlParams); + + auto ltcParams = geos::algorithm::LineToCurveParams(); + ltcParams.setRadiusTolerance(1e-6); + ltcParams.setMaxStepDegrees(stepSizeDegrees*1.001); + + auto outCurve = linearized->getCurved(ltcParams); + auto expected = reader_.read(wkt_expected); + + std::cout << outCurve->toString() << std::endl; + + double distanceTolerance = 1e-4; + + ensure_equals_exact_geometry_xyzm(outCurve.get(), expected.get(), distanceTolerance); + } + + void checkRoundTripUnchanged(const std::string& wkt_in, double stepSizeDegrees) const { + checkRoundTrip(wkt_in, wkt_in, stepSizeDegrees); + } + + void checkLineToCurve(const std::string& wkt_in, const std::string& wkt_expected, double distanceTolerance, double stepSizeDegrees) const { + auto ls = reader_.read(wkt_in); + + auto ltcParams = geos::algorithm::LineToCurveParams(); + ltcParams.setRadiusTolerance(distanceTolerance); + ltcParams.setMaxStepDegrees(stepSizeDegrees); + + auto curve = ls->getCurved(ltcParams); + auto expected = reader_.read(wkt_expected); + + ensure_equals_exact_geometry_xyzm(curve.get(), expected.get(), distanceTolerance); + } + + void checkLineToCurveUnchanged(const std::string& wkt_in, double distanceTolerance) const { + auto ltcParams = geos::algorithm::LineToCurveParams(); + ltcParams.setRadiusTolerance(distanceTolerance); + + auto ls = reader_.read(wkt_in); + auto expected = ls->clone(); + auto curve = ls->getCurved(ltcParams); + + ensure_equals_exact_geometry_xyzm(curve.get(), expected.get(), 0.0); + } +}; + +typedef test_group group; +typedef group::object object; + +group test_curvebuilder_group("geos::algorithm::CurveBuilder"); + +template<> +template<> +void object::test<1>() +{ + set_test_name("two-point LineString"); + + checkLineToCurveUnchanged("LINESTRING (3 4, 2 7)", 1); +} + +template<> +template<> +void object::test<2>() +{ + set_test_name("LineString of three collinear points"); + + checkLineToCurveUnchanged("LINESTRING (0 0, 1 2, 2 4)", 1); +} + +template<> +template<> +void object::test<3>() +{ + set_test_name("coarsely linearized semicircle"); + + checkLineToCurve("LINESTRING(0 0,29.2893 70.7107,100 100,170.7107 70.7107,200 0)", + "CIRCULARSTRING (0 0, 100 100, 200 0)", + 2e-3, 45.1); +} + +template<> +template<> +void object::test<4>() +{ + set_test_name("coarsely linearized closed semicircle"); + + checkLineToCurve("LINESTRING(0 0,29.2893 70.7107,100 100,170.7107 70.7107,200 0, 0 0)", + "COMPOUNDCURVE (CIRCULARSTRING (0 0, 100 100, 200 0), LINESTRING (200 0, 0 0))", + 2.5e-3, 45.1); +} + +template<> +template<> +void object::test<5>() +{ + set_test_name("round-trip with 3-quadrant CircularString"); + + // cu_lwstroke.c: 392 + checkRoundTrip("CIRCULARSTRING (-1 0, 0 1, 0 -1)", + "CIRCULARSTRING (-1 0, 0.70710678 0.707010678, 0 -1)", + 90.0 / 8); +} + +template<> +template<> +void object::test<6>() +{ + set_test_name("Two-part CompoundCurve round-trip"); + + // cu_lwstroke.c: 404 + checkRoundTrip("COMPOUNDCURVE (CIRCULARSTRING (-1 0, 0 1, 0 -1), (0 -1, -1 -1))", + "COMPOUNDCURVE( CIRCULARSTRING (-1 0, 0.70710678 0.707010678, 0 -1), (0 -1, -1 -1))", + 90.0 / 8); +} + +template<> +template<> +void object::test<7>() +{ + set_test_name("Three-part CompoundCurve round-trip"); + + // cu_lwstroke.c: 416 + checkRoundTrip("COMPOUNDCURVE((-3 -3,-1 0),CIRCULARSTRING(-1 0,0 1,0 -1),(0 -1,0 -1.5,0 -2),CIRCULARSTRING(0 -2,-1 -3,1 -3),(1 -3,5 5))", + "COMPOUNDCURVE((-3 -3,-1 0),CIRCULARSTRING(-1 0,0.70710678 0.70710678,0 -1),(0 -1,0 -1.5,0 -2),CIRCULARSTRING(0 -2,-0.70710678 -3.70710678,1 -3),(1 -3,5 5))", + 90.0 / 8); +} + +template<> +template<> +void object::test<8>() +{ + set_test_name("CompoundCurve with two CircularStrings, round-trip"); + + // cu_lwstroke.c: 432 + // NOTE: The expected result here is modified from the original. It is not clear why liblwgeom returns a + // CompoundCurve with two CircularStrings instead of a single CircularString (like GDAL). The expected result + // used here is from GDAL. + checkRoundTrip("COMPOUNDCURVE(CIRCULARSTRING(-1 0,0 1,0 -1),CIRCULARSTRING(0 -1,-1 -2,1 -2))", + "CIRCULARSTRING(-1 0,0.70710678 0.70710678,0 -1,-0.70710678 -2.70710678,1 -2)", + 90.0 / 8); +} + +template<> +template<> +void object::test<9>() +{ + set_test_name("CompoundCurve with a CircularString between two LineStrings, round-trip"); + + // cu_lwstroke.c: 447 + checkRoundTripUnchanged("COMPOUNDCURVE((0 0, 1 1), CIRCULARSTRING(1 1, 2 2, 3 1), (3 1, 4 4))", + 90.0 / 8); +} + +template<> +template<> +void object::test<10>() +{ + set_test_name("LineString forming a square, round-trip"); + + // cu_lwstroke.c: 461 + checkRoundTripUnchanged("LINESTRING(0 0,10 0,10 10,0 10,0 0)", + 90.0 / 8); + + // cu_lwstroke.c: 469 + checkRoundTripUnchanged("LINESTRING(10 10,0 10,0 0,10 0)", + 90.0 / 8); + + // cu_lwstroke.c: 478 + checkRoundTripUnchanged("LINESTRING(0 0,10 0,10 10,0 10)", + 90.0 / 8); +} + +template<> +template<> +void object::test<11>() +{ + set_test_name("collection of two-point LineStrings, round-trip"); + + // cu_lwstroke.c: 497 + checkRoundTripUnchanged("GEOMETRYCOLLECTION(LINESTRING(10 10,10 11),LINESTRING(10 11,11 11),LINESTRING(11 11,10 10))", + 90.0 / 8); +} + +template<> +template<> +void object::test<13>() +{ + set_test_name("collection of two-point LineStrings and a CircularString, round-trip"); + + // cu_lwstroke.c: 508 + checkRoundTripUnchanged("GEOMETRYCOLLECTION(LINESTRING(4 4,4 8),CIRCULARSTRING(4 8,6 10,8 8),LINESTRING(8 8,8 4))", + 90.0 / 8); +} + +template<> +template<> +void object::test<14>() +{ + set_test_name("round-trip with 5-point CircularString"); + + checkRoundTripUnchanged("CIRCULARSTRING (-5 0, 0 5, 5 0, 4 1, 3 0)", + 90.0 / 4); +} + +template<> +template<> +void object::test<15>() +{ + set_test_name("constructed curve does not depend on direction of input"); + + auto cs = reader_.read("CIRCULARSTRING (-5 0, 0 5, 5 0)"); + + auto lin = cs->getLinearized(CurveToLineParams::stepSizeDegrees(90.0 / 4)); + auto linRev = lin->reverse(); + + geos::algorithm::LineToCurveParams ltc; + ltc.setRadiusTolerance(1e-4); + + auto curveRev = linRev->getCurved(ltc); + auto curve1 = curveRev->reverse(); + + auto curve2 = lin->getCurved(ltc); + + ensure_equals_exact_geometry_xyzm(curve1.get(), curve2.get(), 0); +} + +template<> +template<> +void object::test<16>() +{ + set_test_name("Z/M preserved in round-trip"); + + // Midpoint Z/M halfway between start/end + // Linearized arc has an odd number of vertices + checkRoundTripUnchanged("CIRCULARSTRING (0 0 1 7, 1 1 2 9, 2 0 3 11)", 22.5); + + // Midpoint Z/M not halfway between start/end + // Linearized arc has an odd number of vertices + checkRoundTripUnchanged("CIRCULARSTRING (0 0 1 7, 1 1 2 9, 2 0 4 13)", 22.5); + + // Midpoint Z/M halfway between start/end + // Linearized arc has an even number of vertices + checkRoundTripUnchanged("CIRCULARSTRING (0 0 1 7, 1 1 2 9, 2 0 3 11)", 20); + + // Midpoint Z/M not halfway between start/end + // Linearized arc has an odd number of vertices + checkRoundTripUnchanged("CIRCULARSTRING (0 0 1 7, 1 1 2 9, 2 0 4 13)", 20); +} + +template<> +template<> +void object::test<17>() { + set_test_name("big coordinates"); + + checkRoundTripUnchanged("CIRCULARSTRING (426857.987717275 5427937.52346616,500000.000000001 5538630.70286887,573142.012282726 5427937.52346616)", 4); + + checkRoundTripUnchanged("CIRCULARSTRING (426858 5427938,500000 5538632,573142 5427938)", 4); +} + +template<> +template<> +void object::test<18>() +{ + set_test_name("full circle, 2D"); + + auto geom = reader_.read("POINT (0 0)")->buffer(5); + LineToCurveParams params; + auto curved = geom->getCurved(params); + + auto expected = reader_.read("CURVEPOLYGON (CIRCULARSTRING (5 0, 0 -5, -5 0, 0 5, 5 0))"); + + ensure_equals_exact_geometry_xyzm(curved.get(), expected.get(), 1e-8); +} + +template<> +template<> +void object::test<19>() +{ + set_test_name("full circle, XYZM"); + + // with step size of 11.25 degrees, control points of the arc will appear in linearized + // arc, so their Z/M values can be recovered for the result arcs + checkRoundTripUnchanged("CIRCULARSTRING ZM (-5 0 4 3, 0 5 8 6, 5 0 12 9, 0 -5 15 11, -5 0 18 13)", 11.25); + + // with step size of 4 degrees, Z/M values for the result arc control points must be + // interpolated from points in the linearized arc. + checkRoundTripUnchanged("CIRCULARSTRING ZM (-5 0 4 3, 0 5 8 6, 5 0 12 9, 0 -5 15 11, -5 0 18 13)", 4); +} + + +} diff --git a/tests/unit/capi/GEOSAreaTest.cpp b/tests/unit/capi/GEOSAreaTest.cpp new file mode 100644 index 0000000000..2c428004cc --- /dev/null +++ b/tests/unit/capi/GEOSAreaTest.cpp @@ -0,0 +1,35 @@ +#include +// geos +#include +#include + +#include "capi_test_utils.h" + +namespace tut { + // + // Test Group + // + + struct test_geosarea_data : public capitest::utility {}; + + typedef test_group group; + typedef group::object object; + + group test_geosarea("capi::GEOSArea"); + + template<> + template<> + void object::test<1>() + { + set_test_name("curved inputs"); + + input_ = fromWKT("CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + ensure(input_ != nullptr); + + double area = -1; + int ret = GEOSArea(input_, &area); + ensure_equals(ret, 1); + ensure_equals(area, geos::MATH_PI / 2); + } + +} // namespace tut diff --git a/tests/unit/capi/GEOSBufferTest.cpp b/tests/unit/capi/GEOSBufferTest.cpp index f8a555bf32..458b01ce94 100644 --- a/tests/unit/capi/GEOSBufferTest.cpp +++ b/tests/unit/capi/GEOSBufferTest.cpp @@ -23,9 +23,13 @@ struct test_capigeosbuffer_data : public capitest::utility : bp_(nullptr) {} - ~test_capigeosbuffer_data() + ~test_capigeosbuffer_data() override { - GEOSBufferParams_destroy(bp_); + if (ctxt_) { + GEOSBufferParams_destroy_r(ctxt_, bp_); + } else { + GEOSBufferParams_destroy(bp_); + } } }; @@ -465,11 +469,44 @@ template<> template<> void object::test<26>() { + set_test_name("curved input"); + useContext(); + + bp_ = GEOSBufferParams_create_r(ctxt_); + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(input_ != nullptr); - result_ = GEOSBuffer(input_, 1, 8); + result_ = GEOSBuffer_r(ctxt_, input_, 1, 8); + ensure(result_ == nullptr); + + result_ = GEOSBufferWithStyle_r(ctxt_, input_, 1, 8, + GEOSBUF_CAP_ROUND, + GEOSBUF_JOIN_BEVEL, + 5.0); + ensure(result_ == nullptr); + + result_ = GEOSBufferWithParams_r(ctxt_, input_, bp_, 1); ensure(result_ == nullptr); + + useCurveConversion(); + + result_ = GEOSBuffer_r(ctxt_, input_, 1, 8); + ensure(result_ != nullptr); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); + GEOSGeom_destroy_r(ctxt_, result_); + + result_ = GEOSBufferWithStyle_r(ctxt_, input_, 1, 8, + GEOSBUF_CAP_ROUND, + GEOSBUF_JOIN_BEVEL, + 5.0); + ensure(result_ != nullptr); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); + GEOSGeom_destroy_r(ctxt_, result_); + + result_ = GEOSBufferWithParams_r(ctxt_, input_, bp_, 1); + ensure(result_ != nullptr); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); } template<> diff --git a/tests/unit/capi/GEOSClipByRectTest.cpp b/tests/unit/capi/GEOSClipByRectTest.cpp index c1c77aace8..31eacd2f49 100644 --- a/tests/unit/capi/GEOSClipByRectTest.cpp +++ b/tests/unit/capi/GEOSClipByRectTest.cpp @@ -294,11 +294,28 @@ template<> template<> void object::test<17>() { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(input_ != nullptr); - result_ = GEOSClipByRect(input_, 0, 0, 1, 1); + result_ = GEOSClipByRect_r(ctxt_, input_, 0, 0, 1, 1); ensure(result_ == nullptr); + + GEOSCurveToLineParams_setTolerance_r(ctxt_, curveToLineParams_, GEOS_CURVETOLINE_STEP_DEGREES, 1); + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + + // Input converted to line, output not converted to curve + result_ = GEOSClipByRect_r(ctxt_, input_, 0, 0, 1, 1); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_LINESTRING); + GEOSGeom_destroy_r(ctxt_, result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSClipByRect_r(ctxt_, input_, 0, 0, 1, 1); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_CIRCULARSTRING); } /// Point Z inside diff --git a/tests/unit/capi/GEOSClusterTest.cpp b/tests/unit/capi/GEOSClusterTest.cpp index c8136720f7..bb9b914491 100644 --- a/tests/unit/capi/GEOSClusterTest.cpp +++ b/tests/unit/capi/GEOSClusterTest.cpp @@ -4,6 +4,7 @@ #include // geos #include +#include #include "capi_test_utils.h" @@ -163,6 +164,61 @@ void object::test<2>() } } +template<> +template<> +void object::test<3>() +{ + set_test_name("curved inputs"); + useContext(); + + input_ = fromWKT( + "GEOMETRYCOLLECTION (" + "POINT (-2 0)," + "POINT (2 0)," + "CIRCULARSTRING (-1 0, 0 1, 1 0)," + "LINESTRING (0 0, 1 0)" + ")"); + + // EnvelopeDistance supports curves + { + GEOSClusterInfo* clusters = GEOSClusterEnvelopeDistance_r(ctxt_, input_, 1); + ensure_equals("EnvelopeDistance=1", GEOSClusterInfo_getNumClusters_r(ctxt_, clusters), 1u); + GEOSClusterInfo_destroy_r(ctxt_, clusters); + } + + // EnvelopeIntersects supports curves + { + GEOSClusterInfo* clusters = GEOSClusterEnvelopeIntersects_r(ctxt_, input_); + ensure_equals("EnvelopeIntersects", GEOSClusterInfo_getNumClusters_r(ctxt_, clusters), 3u); + GEOSClusterInfo_destroy_r(ctxt_, clusters); + } + + // These cluster methods do not support curves + ensure(GEOSClusterDBSCAN_r(ctxt_, input_, 0.9, 0) == nullptr); + ensure(GEOSClusterGeometryDistance_r(ctxt_, input_, 0.9) == nullptr); + ensure(GEOSClusterGeometryIntersects_r(ctxt_, input_) == nullptr); + + useCurveConversion(); + + { + GEOSClusterInfo* clusters = GEOSClusterDBSCAN_r(ctxt_, input_, 0.9, 0); + ensure_equals("DBSCAN", GEOSClusterInfo_getNumClusters_r(ctxt_, clusters), 3u); + GEOSClusterInfo_destroy_r(ctxt_, clusters); + } + + { + GEOSClusterInfo* clusters = GEOSClusterGeometryDistance_r(ctxt_, input_, 0.9); + ensure_equals("GeometryDistance", GEOSClusterInfo_getNumClusters_r(ctxt_, clusters), 3u); + GEOSClusterInfo_destroy_r(ctxt_, clusters); + } + + { + GEOSClusterInfo* clusters = GEOSClusterGeometryIntersects_r(ctxt_, input_); + ensure_equals("GeometryIntersects", GEOSClusterInfo_getNumClusters_r(ctxt_, clusters), 3u); + GEOSClusterInfo_destroy_r(ctxt_, clusters); + } +} + } // namespace tut diff --git a/tests/unit/capi/GEOSConcaveHullOfPolygonsTest.cpp b/tests/unit/capi/GEOSConcaveHullOfPolygonsTest.cpp index 751e4cdc40..fe12e4f2d0 100644 --- a/tests/unit/capi/GEOSConcaveHullOfPolygonsTest.cpp +++ b/tests/unit/capi/GEOSConcaveHullOfPolygonsTest.cpp @@ -1,5 +1,3 @@ -// -// Test Suite for C-API GEOSConcaveHull #include // geos @@ -53,11 +51,27 @@ template<> template<> void object::test<3>() { - input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0) ))"); + set_test_name("curved inputs"); + useContext(); + + input_ = fromWKT("MULTISURFACE (" + "CURVEPOLYGON (CIRCULARSTRING (0 0, 1 1, 2 0, 1 -1, 0 0))," + "CURVEPOLYGON (CIRCULARSTRING (4 0, 5 1, 6 0, 5 -1, 4 0))," + "CURVEPOLYGON (CIRCULARSTRING (2 2, 3 3, 4 2, 3 1, 2 2))" + ")"); ensure(input_ != nullptr); - result_ = GEOSConcaveHullOfPolygons(input_, 0.7, false, false); + printf("%s", toWKT(input_).c_str()); + + result_ = GEOSConcaveHullOfPolygons_r(ctxt_, input_, 0.7, false, false); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + + result_ = GEOSConcaveHullOfPolygons_r(ctxt_, input_, 0.3, false, false); + ensure(result_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); } template<> diff --git a/tests/unit/capi/GEOSConcaveHullTest.cpp b/tests/unit/capi/GEOSConcaveHullTest.cpp index 2984418ed8..a995db9e0b 100644 --- a/tests/unit/capi/GEOSConcaveHullTest.cpp +++ b/tests/unit/capi/GEOSConcaveHullTest.cpp @@ -54,21 +54,42 @@ template<> template<> void object::test<3>() { - input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + set_test_name("curved inputs"); + useContext(); + + input_ = fromWKT("CURVEPOLYGON (CIRCULARSTRING (0 0, 1 1, 2 0, 1 0.8, 0 0))"); ensure(input_ != nullptr); - result_ = GEOSConcaveHull(input_, 0, 0); + result_ = GEOSConcaveHull_r(ctxt_, input_, 0.5, 0); + ensure(result_ == nullptr); + + result_ = GEOSConcaveHullByLength_r(ctxt_, input_, 10, 0); ensure(result_ == nullptr); + + useCurveConversion(); + + result_ = GEOSConcaveHull_r(ctxt_, input_, 0.5, 0); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); + GEOSGeom_destroy_r(ctxt_, result_); + + result_ = GEOSConcaveHullByLength_r(ctxt_, input_, 10, 0); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); } template<> template<> void object::test<4>() { + set_test_name("MULTIPOINT ZM"); + input_ = fromWKT("MULTIPOINT ZM (0 0 1 2, 1 0 3 4, 1 1 5 6, 1 8 11 4, 0.5 0.5 -4 -7)"); result_ = GEOSConcaveHull(input_, 0, 0); - printf("%s", toWKT(result_).c_str()); + ensure(result_); + ensure(GEOSHasZ(result_)); + ensure(!GEOSHasM(result_)); } diff --git a/tests/unit/capi/GEOSConstrainedDelaunayTriangulationTest.cpp b/tests/unit/capi/GEOSConstrainedDelaunayTriangulationTest.cpp index 51362108f8..649a442d8f 100644 --- a/tests/unit/capi/GEOSConstrainedDelaunayTriangulationTest.cpp +++ b/tests/unit/capi/GEOSConstrainedDelaunayTriangulationTest.cpp @@ -72,11 +72,21 @@ template<> template<> void object::test<4>() { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); ensure(input_ != nullptr); - result_ = GEOSConstrainedDelaunayTriangulation(input_); + result_ = GEOSConstrainedDelaunayTriangulation_r(ctxt_, input_); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + + result_ = GEOSConstrainedDelaunayTriangulation_r(ctxt_, input_); + ensure(result_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_GEOMETRYCOLLECTION); } template<> diff --git a/tests/unit/capi/GEOSContainsTest.cpp b/tests/unit/capi/GEOSContainsTest.cpp index 3d377e8295..e7132183bc 100644 --- a/tests/unit/capi/GEOSContainsTest.cpp +++ b/tests/unit/capi/GEOSContainsTest.cpp @@ -192,15 +192,20 @@ template<> template<> void object::test<6>() { - geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); - geom2_ = fromWKT("LINESTRING (1 0, 2 0)"); + set_test_name("GEOSContains with automatic linearization"); + useContext(); + + geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + geom2_ = fromWKT("LINESTRING (1 0.5, 1 0.6)"); ensure(geom1_); ensure(geom2_); - ensure_equals("curved geometry not supported", GEOSContains(geom1_, geom2_), 2); + ensure_equals(GEOSContains_r(ctxt_, geom1_, geom2_), 2); + useCurveConversion(); + ensure_equals(GEOSContains_r(ctxt_, geom1_, geom2_), 1); + ensure_equals(GEOSContains_r(ctxt_, geom2_, geom1_), 0); } - } // namespace tut diff --git a/tests/unit/capi/GEOSConvexHullTest.cpp b/tests/unit/capi/GEOSConvexHullTest.cpp index adb679cca8..020664b703 100644 --- a/tests/unit/capi/GEOSConvexHullTest.cpp +++ b/tests/unit/capi/GEOSConvexHullTest.cpp @@ -43,11 +43,20 @@ template<> template<> void object::test<2>() { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(input_ != nullptr); - result_ = GEOSConvexHull(input_); + result_ = GEOSConvexHull_r(ctxt_, input_); ensure(result_ == nullptr); + + useCurveConversion(); + result_ = GEOSConvexHull_r(ctxt_, input_); + ensure(result_ != nullptr); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); } } // namespace tut diff --git a/tests/unit/capi/GEOSCoverageCleanTest.cpp b/tests/unit/capi/GEOSCoverageCleanTest.cpp new file mode 100644 index 0000000000..ffdca6dbae --- /dev/null +++ b/tests/unit/capi/GEOSCoverageCleanTest.cpp @@ -0,0 +1,60 @@ +#include +// geos +#include + +#include "capi_test_utils.h" + +namespace tut { +// +// Test Group +// + +struct test_geoscoverageclean_data : public capitest::utility +{ + GEOSCoverageCleanParams* ccp_ = nullptr; + + ~test_geoscoverageclean_data() override { + if (ctxt_) { + GEOSCoverageCleanParams_destroy_r(ctxt_, ccp_); + } else { + GEOSCoverageCleanParams_destroy(ccp_); + } + } +}; + +typedef test_group group; +typedef group::object object; + +group test_geoscoverageclean("capi::GEOSCoverageClean"); + +template<> +template<> +void object::test<1>() +{ + set_test_name("curved inputs"); + useContext(); + + input_ = fromWKT("GEOMETRYCOLLECTION (" + "POLYGON ((0 0, 10 0, 10.0001 5, 10 10, 0 10, 0 0))," + "CURVEPOLYGON (COMPOUNDCURVE ((20 0, 10 0, 10 10, 20 10), CIRCULARSTRING (20 10, 25 5, 20 0))))"); + + result_ = GEOSCoverageClean_r(ctxt_, input_); + ensure(!result_); + + ccp_ = GEOSCoverageCleanParams_create_r(ctxt_); + result_ = GEOSCoverageCleanWithParams_r(ctxt_, input_, ccp_); + ensure(!result_); + + useCurveConversion(); + + result_ = GEOSCoverageClean_r(ctxt_, input_); + ensure(result_); + + expected_ = fromWKT("GEOMETRYCOLLECTION (" + "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))," + "CURVEPOLYGON (COMPOUNDCURVE ((20 0, 10 0, 10 10, 20 10), CIRCULARSTRING (20 10, 25 5, 20 0))))"); + + ensure_geometry_equals(result_, expected_); +} +} // namespace tut + diff --git a/tests/unit/capi/GEOSCoverageIsValidTest.cpp b/tests/unit/capi/GEOSCoverageIsValidTest.cpp index 8f1e028894..2cf0888686 100644 --- a/tests/unit/capi/GEOSCoverageIsValidTest.cpp +++ b/tests/unit/capi/GEOSCoverageIsValidTest.cpp @@ -77,22 +77,25 @@ template<> void object::test<2> expected_ = fromWKT(expectedWKT); - // std::cout << toWKT(result_) << std::endl; - // std::cout << toWKT(expected_) << std::endl; - ensure_geometry_equals(result_, expected_, 0.01); } template<> -template<> void object::test<3> +template<> +void object::test<3> () { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("GEOMETRYCOLLECTION ( " - "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 0, 1 1, 2 2), (2 2, 0 2, 0 0, 2 0))), " - "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 2, 1 1, 2 0), (2 0, 4 0, 4 2, 2 2))))"); - ensure(input_); + "CURVEPOLYGON (COMPOUNDCURVE ( (4 0, 0 0, 0 4, 4 4), CIRCULARSTRING (4 4, 2 2, 4 0))), " + "CURVEPOLYGON (CIRCULARSTRING (4 4, 6 2, 4 0, 2 2, 4 4)))"); + + ensure_equals("curved geometry not supported", GEOSCoverageIsValid_r(ctxt_, input_, 0, nullptr), 2); - ensure_equals("curved geometry not supported", GEOSCoverageIsValid(input_, 0, nullptr), 2); + useCurveConversion(); + ensure_equals(GEOSCoverageIsValid_r(ctxt_, input_, 0, nullptr), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSCoverageSimplifyTest.cpp b/tests/unit/capi/GEOSCoverageSimplifyTest.cpp index 4ca7e9c394..d22ae9b799 100644 --- a/tests/unit/capi/GEOSCoverageSimplifyTest.cpp +++ b/tests/unit/capi/GEOSCoverageSimplifyTest.cpp @@ -96,16 +96,28 @@ template<> void object::test<3> } template<> -template<> void object::test<4> +template<> +void object::test<4> () { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("GEOMETRYCOLLECTION ( " - "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 0, 1 1, 2 2), (2 2, 0 2, 0 0, 2 0))), " - "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 2, 1 1, 2 0), (2 0, 4 0, 4 2, 2 2))))"); - ensure(input_); + "CURVEPOLYGON (COMPOUNDCURVE ( (4 0, 0 0, 0 4, 4 4), CIRCULARSTRING (4 4, 3.95 2, 4 0))), " + "CURVEPOLYGON (CIRCULARSTRING (4 4, 6 2, 4 0, 3.95 2, 4 4)))"); - result_ = GEOSCoverageSimplifyVW(input_, 0.1, false); + result_ = GEOSCoverageSimplifyVW_r(ctxt_, input_, 1, false); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + + result_ = GEOSCoverageSimplifyVW_r(ctxt_, input_, 0.2, false); + ensure(result_); + + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 2); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0)), GEOS_POLYGON); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 1)), GEOS_CURVEPOLYGON); } diff --git a/tests/unit/capi/GEOSCoverageUnionTest.cpp b/tests/unit/capi/GEOSCoverageUnionTest.cpp index 1e0d7d67d9..8bc1ca186a 100644 --- a/tests/unit/capi/GEOSCoverageUnionTest.cpp +++ b/tests/unit/capi/GEOSCoverageUnionTest.cpp @@ -78,13 +78,27 @@ template<> template<> void object::test<4> () { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("GEOMETRYCOLLECTION ( " - "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 0, 1 1, 2 2), (2 2, 0 2, 0 0, 2 0))), " - "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 2, 1 1, 2 0), (2 0, 4 0, 4 2, 2 2))))"); - ensure(input_); + "CURVEPOLYGON (COMPOUNDCURVE ( (4 0, 0 0, 0 4, 4 4), CIRCULARSTRING (4 4, 2 2, 4 0))), " + "CURVEPOLYGON (CIRCULARSTRING (4 4, 6 2, 4 0, 2 2, 4 4)))"); - result_ = GEOSCoverageSimplifyVW(input_, 0.1, false); + result_ = GEOSCoverageUnion_r(ctxt_, input_); ensure("curved geometry not supported", result_ == nullptr); + + // Input converted to line, output not converted to curve + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + result_ = GEOSCoverageUnion_r(ctxt_, input_); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); + GEOSGeom_destroy_r(ctxt_, result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSCoverageUnion_r(ctxt_, input_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_CURVEPOLYGON); } } // namespace tut diff --git a/tests/unit/capi/GEOSCoveredByTest.cpp b/tests/unit/capi/GEOSCoveredByTest.cpp index 6982fb4966..68a6bed6fc 100644 --- a/tests/unit/capi/GEOSCoveredByTest.cpp +++ b/tests/unit/capi/GEOSCoveredByTest.cpp @@ -36,13 +36,19 @@ template<> template<> void object::test<2>() { - geom1_ = fromWKT("LINESTRING (5 3, 5 4)"); - geom2_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)))"); + set_test_name("GEOSCoveredBy with automatic linearization"); + useContext(); + + geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + geom2_ = fromWKT("LINESTRING (1 0.5, 1 0.6)"); ensure(geom1_); ensure(geom2_); - ensure_equals("curved geometry not supported", GEOSCoveredBy(geom1_, geom2_), 2); + ensure_equals(GEOSCoveredBy_r(ctxt_, geom2_, geom1_), 2); + useCurveConversion(); + ensure_equals(GEOSCoveredBy_r(ctxt_, geom1_, geom2_), 0); + ensure_equals(GEOSCoveredBy_r(ctxt_, geom2_, geom1_), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSCoversTest.cpp b/tests/unit/capi/GEOSCoversTest.cpp index bdf5f3b087..e3c9d2794e 100644 --- a/tests/unit/capi/GEOSCoversTest.cpp +++ b/tests/unit/capi/GEOSCoversTest.cpp @@ -36,13 +36,19 @@ template<> template<> void object::test<2>() { - geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)))"); - geom2_ = fromWKT("LINESTRING (5 3, 5 4)"); + set_test_name("GEOSCovers with automatic linearization"); + useContext(); + + geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + geom2_ = fromWKT("LINESTRING (1 0.5, 1 0.6)"); ensure(geom1_); ensure(geom2_); - ensure_equals("curved geometry not supported", GEOSCovers(geom1_, geom2_), 2); + ensure_equals(GEOSCovers_r(ctxt_, geom1_, geom2_), 2); + useCurveConversion(); + ensure_equals(GEOSCovers_r(ctxt_, geom1_, geom2_), 1); + ensure_equals(GEOSCovers_r(ctxt_, geom2_, geom1_), 0); } } // namespace tut diff --git a/tests/unit/capi/GEOSCrossesTest.cpp b/tests/unit/capi/GEOSCrossesTest.cpp index 12ba5f161a..6927eb6218 100644 --- a/tests/unit/capi/GEOSCrossesTest.cpp +++ b/tests/unit/capi/GEOSCrossesTest.cpp @@ -36,14 +36,22 @@ template<> template<> void object::test<2>() { + set_test_name("GEOSCrosses with automatic linearization"); + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); ensure(geom1_); ensure(geom2_); - ensure_equals("curved geometry not supported", GEOSCrosses(geom1_, geom2_), 2); - ensure_equals("curved geometry not supported", GEOSCrosses(geom2_, geom1_), 2); + ensure_equals(GEOSCrosses_r(ctxt_, geom1_, geom2_), 2); + ensure_equals(GEOSCrosses_r(ctxt_, geom2_, geom1_), 2); + + useCurveConversion(); + + ensure_equals(GEOSCrosses_r(ctxt_, geom1_, geom2_), 1); + ensure_equals(GEOSCrosses_r(ctxt_, geom2_, geom1_), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSCurveToLineTest.cpp b/tests/unit/capi/GEOSCurveToLineTest.cpp new file mode 100644 index 0000000000..a708d35262 --- /dev/null +++ b/tests/unit/capi/GEOSCurveToLineTest.cpp @@ -0,0 +1,109 @@ +#include +// geos +#include +// std +#include + +#include "capi_test_utils.h" + +namespace tut { + +// +// Test Group +// +struct test_capigeoscurvetoline_data : public capitest::utility { + test_capigeoscurvetoline_data() { + params_ = GEOSCurveToLineParams_create(); + } + + ~test_capigeoscurvetoline_data() override { + GEOSCurveToLineParams_destroy(params_); + } + + GEOSCurveToLineParams* params_; +}; + +using group = test_group; +using object = group::object; + +group test_capigeoscurvetoline_group("capi::GEOSCurveToLine"); + +// +// Test Cases +// + +template<> +template<> +void object::test<1>() +{ + set_test_name("empty input converted to linear type"); + + input_ = fromWKT("CIRCULARSTRING EMPTY"); + result_ = GEOSCurveToLine(input_, params_); + ensure_equals(GEOSGeomTypeId(result_), GEOS_LINESTRING); + ensure(GEOSisEmpty(result_)); +} + +template<> +template<> +void object::test<2>() +{ + set_test_name("linear input copied"); + + input_ = fromWKT("LINESTRING (0 0, 1 1, 2 0)"); + result_ = GEOSCurveToLine(input_, params_); + + ensure_geometry_equals_identical(result_, input_); +} + +template<> +template<> +void object::test<3>() +{ + set_test_name("CircularString, step size degrees"); + + input_ = fromWKT("CIRCULARSTRING (0 0, 100 100, 200 0)"); + + ensure(GEOSCurveToLineParams_setTolerance(params_, GEOS_CURVETOLINE_STEP_DEGREES, 30)); + result_ = GEOSCurveToLine(input_, params_); + expected_ = fromWKT("LINESTRING(0 0,13.3975 50,50 86.6025,100 100,150 86.6025,186.6025 50,200 0)"); + + ensure_geometry_equals_exact(result_, expected_, 1e-3); +} + +template<> +template<> +void object::test<4>() +{ + set_test_name("CircularString, maximum deviation"); + + input_ = fromWKT("CIRCULARSTRING (0 0, 100 100, 200 0)"); + ensure(GEOSCurveToLineParams_setTolerance(params_, GEOS_CURVETOLINE_MAX_DEVIATION, 10)); + result_ = GEOSCurveToLine(input_, params_); + expected_ = fromWKT("LINESTRING(0 0,30 70,100 100,170 70,200 0)"); + + ensure_geometry_equals_exact(result_, expected_, 1.4); +} + +template<> +template<> +void object::test<5>() +{ + set_test_name("invalid tolerance type"); + + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(!GEOSCurveToLineParams_setTolerance(params_, -3, 10)); +} + +template<> +template<> +void object::test<6>() +{ + set_test_name("invalid tolerance value"); + + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(!GEOSCurveToLineParams_setTolerance(params_, GEOS_CURVETOLINE_STEP_DEGREES, -1)); + ensure(!GEOSCurveToLineParams_setTolerance(params_, GEOS_CURVETOLINE_STEP_DEGREES, std::numeric_limits::quiet_NaN())); +} + +} // namespace tut \ No newline at end of file diff --git a/tests/unit/capi/GEOSDelaunayTriangulationTest.cpp b/tests/unit/capi/GEOSDelaunayTriangulationTest.cpp index f0f2ab0774..a55ec6a903 100644 --- a/tests/unit/capi/GEOSDelaunayTriangulationTest.cpp +++ b/tests/unit/capi/GEOSDelaunayTriangulationTest.cpp @@ -127,11 +127,16 @@ template<> template<> void object::test<7>() { + set_test_name("curved inputs"); + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0)"); ensure(input_); result_ = GEOSDelaunayTriangulation(input_, 0, 0); - ensure("curved geometry not supported", result_ == nullptr); + ensure(result_); + + ensure_equals(GEOSGeomTypeId(result_), GEOS_GEOMETRYCOLLECTION); + ensure_equals(GEOSGetNumGeometries(result_), 4); } template<> diff --git a/tests/unit/capi/GEOSDensifyTest.cpp b/tests/unit/capi/GEOSDensifyTest.cpp index 752441ddbf..43e87d5693 100644 --- a/tests/unit/capi/GEOSDensifyTest.cpp +++ b/tests/unit/capi/GEOSDensifyTest.cpp @@ -163,11 +163,19 @@ template<> template<> void object::test<10>() { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(input_); - result_ = GEOSDensify(input_, 0.1); + result_ = GEOSDensify_r(ctxt_, input_, 0.1); ensure("curved geometries not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSDensify_r(ctxt_, input_, 0.1); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_LINESTRING); } // Densify a LINESTRING Z, check that Z gets interpolated diff --git a/tests/unit/capi/GEOSDifferenceTest.cpp b/tests/unit/capi/GEOSDifferenceTest.cpp index d739b09361..de03464fdd 100644 --- a/tests/unit/capi/GEOSDifferenceTest.cpp +++ b/tests/unit/capi/GEOSDifferenceTest.cpp @@ -63,14 +63,36 @@ template<> template<> void object::test<3>() { + set_test_name("curved inputs, curved output"); + + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); - geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + geom2_ = fromWKT("POLYGON ((1 0, 2 0, 2 1, 1 1, 1 0))"); ensure(geom1_); ensure(geom2_); + GEOSSetSRID_r(ctxt_, geom1_, 4326); - result_ = GEOSDifference(geom1_, geom2_); + result_ = GEOSDifference_r(ctxt_, geom1_, geom2_); ensure("curved geometry not supported", result_ == nullptr); + + GEOSCurveToLineParams_setTolerance_r(ctxt_, curveToLineParams_, GEOS_CURVETOLINE_STEP_DEGREES, 1); + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + + // Input converted to line, output not converted to curve + result_ = GEOSDifference_r(ctxt_, geom1_, geom2_); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_LINESTRING); + GEOSGeom_destroy_r(ctxt_, result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSDifference_r(ctxt_, geom1_, geom2_); + expected_ = fromWKT("CIRCULARSTRING (0 0, 0.292893 0.707107, 1 1)"); + ensure_geometry_equals(result_, expected_, 1e-6); + + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); } } // namespace tut diff --git a/tests/unit/capi/GEOSDisjointSubsetUnionTest.cpp b/tests/unit/capi/GEOSDisjointSubsetUnionTest.cpp index 76e2a39923..54c64558a6 100644 --- a/tests/unit/capi/GEOSDisjointSubsetUnionTest.cpp +++ b/tests/unit/capi/GEOSDisjointSubsetUnionTest.cpp @@ -48,12 +48,36 @@ void object::test<2>() template <> template <> -void object::test<3>() { - input_ = fromWKT("MULTICURVE ((0 0, 1 1), CIRCULARSTRING (1 1, 2 0, 3 1), (5 5, 8 8))"); +void object::test<3>() +{ + set_test_name("curved inputs"); + useContext(); + + input_ = fromWKT("MULTISURFACE (" + "CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING (0 0, 5 5, 10 0), (10 0, 10 -10, 0 -10, 0 0)))," + "((10 -10, 20 -10, 20 0, 10 0, 10 -10))," + "CURVEPOLYGON (CIRCULARSTRING (100 100, 110 110, 120 100, 110 90, 100 100)))"); ensure(input_); - result_ = GEOSDisjointSubsetUnion(input_); + result_ = GEOSDisjointSubsetUnion_r(ctxt_, input_); ensure("curved geometry not supported", result_ == nullptr); + + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + + // Input converted to line, output not converted to curve + result_ = GEOSDisjointSubsetUnion_r(ctxt_, input_); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTIPOLYGON); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 2); + GEOSGeom_destroy_r(ctxt_, result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSDisjointSubsetUnion_r(ctxt_, input_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTISURFACE); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 2); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0)), GEOS_CURVEPOLYGON); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 1)), GEOS_CURVEPOLYGON); } } // namespace tut diff --git a/tests/unit/capi/GEOSDisjointTest.cpp b/tests/unit/capi/GEOSDisjointTest.cpp index f17faca3aa..19b8fe138a 100644 --- a/tests/unit/capi/GEOSDisjointTest.cpp +++ b/tests/unit/capi/GEOSDisjointTest.cpp @@ -42,5 +42,21 @@ void object::test<2>() ensure_equals("curved geometry not supported", GEOSDisjoint(geom2_, geom1_), 2); } +template<> +template<> +void object::test<3>() +{ + set_test_name("GEOSDisjoint with automatic linearization"); + + useContext(); + + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("CIRCULARSTRING (2 0, 3 -1, 4 0)"); + + ensure_equals(2, GEOSDisjoint_r(ctxt_, geom1_, geom2_)); + useCurveConversion(); + ensure_equals(0, GEOSDisjoint_r(ctxt_, geom1_, geom2_)); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSDistanceTest.cpp b/tests/unit/capi/GEOSDistanceTest.cpp index 42a895a62b..c053887b53 100644 --- a/tests/unit/capi/GEOSDistanceTest.cpp +++ b/tests/unit/capi/GEOSDistanceTest.cpp @@ -171,15 +171,27 @@ template<> template<> void object::test<6>() { + set_test_name("curved inputs"); + + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); - geom2_ = fromWKT("LINESTRING (1 1.0001, 2 1)"); + geom2_ = fromWKT("LINESTRING (2.0001 0, 3 0)"); ensure(geom1_); ensure(geom2_); double dist; - int ret = GEOSDistance(geom1_, geom2_, &dist); - ensure_equals("curved geometry not supported", ret, 0); + ensure_equals(GEOSDistance_r(ctxt_, geom1_, geom2_, &dist), 0); + ensure_equals(GEOSDistanceIndexed_r(ctxt_, geom1_, geom2_, &dist), 0); + + useCurveConversion(); + ensure_equals(GEOSDistance_r(ctxt_, geom1_, geom2_, &dist), 1); + ensure_equals(dist, 0.0001); + + dist = -1; + ensure_equals(GEOSDistanceIndexed_r(ctxt_, geom1_, geom2_, &dist), 1); + ensure_equals(dist, 0.0001); } template<> diff --git a/tests/unit/capi/GEOSDistanceWithinTest.cpp b/tests/unit/capi/GEOSDistanceWithinTest.cpp index 074c1b59bf..49497388ab 100644 --- a/tests/unit/capi/GEOSDistanceWithinTest.cpp +++ b/tests/unit/capi/GEOSDistanceWithinTest.cpp @@ -412,14 +412,20 @@ template<> template<> void object::test<32>() { + set_test_name("curved inputs"); + + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 1.0001, 2 1)"); ensure(geom1_); ensure(geom2_); - char ret = GEOSDistanceWithin(geom1_, geom2_, 0.1); - ensure_equals("curved geometry not supported", ret, 2); + ensure_equals(GEOSDistanceWithin_r(ctxt_, geom1_, geom2_, 0.1), 2); + + useCurveConversion(); + ensure_equals(GEOSDistanceWithin_r(ctxt_, geom1_, geom2_, 0.1), 1); } template<> diff --git a/tests/unit/capi/GEOSEnvelopeTest.cpp b/tests/unit/capi/GEOSEnvelopeTest.cpp index 68f9317bfa..ca80f5cc32 100644 --- a/tests/unit/capi/GEOSEnvelopeTest.cpp +++ b/tests/unit/capi/GEOSEnvelopeTest.cpp @@ -104,5 +104,15 @@ void object::test<7>() ); } +template<> +template<> +void object::test<9>() +{ + set_test_name("curved inputs"); + + checkEnvelope("CIRCULARSTRING (0 0, 1 1, 2 0)", + "POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))"); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSEqualsExactTest.cpp b/tests/unit/capi/GEOSEqualsExactTest.cpp new file mode 100644 index 0000000000..9c0671dd77 --- /dev/null +++ b/tests/unit/capi/GEOSEqualsExactTest.cpp @@ -0,0 +1,46 @@ +// +// Test Suite for C-API GEOSEquals + +#include +// geos +#include +#include +// std +#include + +#include "capi_test_utils.h" + +namespace tut { +// +// Test Group +// + +// Common data used in test cases. +struct test_capigeosequalsexact_data : public capitest::utility {}; + +typedef test_group group; +typedef group::object object; + +group test_capigeosequalsexact_group("capi::GEOSEqualsExact"); + +// +// Test Cases +// + +template<> +template<> +void object::test<1> +() +{ + set_test_name("almost-identical CircularStrings"); + + geom1_ = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1.001, 2 0)"); + + ensure_equals(GEOSEqualsExact(geom1_, geom2_, 0), 0); + ensure_equals(GEOSEqualsExact(geom1_, geom2_, 1e-6), 0); + ensure_equals(GEOSEqualsExact(geom1_, geom2_, 1e-3), 1); +} + +} // namespace tut + diff --git a/tests/unit/capi/GEOSEqualsTest.cpp b/tests/unit/capi/GEOSEqualsTest.cpp index 392e2cd32a..547c5a3119 100644 --- a/tests/unit/capi/GEOSEqualsTest.cpp +++ b/tests/unit/capi/GEOSEqualsTest.cpp @@ -135,14 +135,23 @@ template<> template<> void object::test<7>() { + set_test_name("GEOSEquals with automatic linearization"); + + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(geom1_); ensure(geom2_); - ensure_equals("curved geometry not supported", GEOSEquals(geom1_, geom2_), 2); - ensure_equals("curved geometry not supported", GEOSEquals(geom2_, geom1_), 2); + ensure_equals(GEOSEquals_r(ctxt_, geom1_, geom2_), 2); + ensure_equals(GEOSEquals_r(ctxt_, geom2_, geom1_), 2); + + useCurveConversion(); + + ensure_equals(GEOSEquals_r(ctxt_, geom1_, geom2_), 1); + ensure_equals(GEOSEquals_r(ctxt_, geom2_, geom1_), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSFrechetDistanceTest.cpp b/tests/unit/capi/GEOSFrechetDistanceTest.cpp index 68d8dddd51..63767bb413 100644 --- a/tests/unit/capi/GEOSFrechetDistanceTest.cpp +++ b/tests/unit/capi/GEOSFrechetDistanceTest.cpp @@ -70,6 +70,10 @@ template<> template<> void object::test<5>() { + set_test_name("curved inputs"); + + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 2, 2 2)"); @@ -77,8 +81,16 @@ void object::test<5>() ensure(geom2_); double dist; - ensure_equals("curved geometry not supported", GEOSFrechetDistance(geom1_, geom2_, &dist), 0); - ensure_equals("curved geometry not supported", GEOSFrechetDistance(geom2_, geom1_, &dist), 0); + ensure_equals(GEOSFrechetDistance_r(ctxt_, geom1_, geom2_, &dist), 0); + ensure_equals(GEOSFrechetDistance_r(ctxt_, geom2_, geom1_, &dist), 0); + ensure_equals(GEOSFrechetDistanceDensify_r(ctxt_, geom1_, geom2_, 0.5, &dist), 0); + ensure_equals(GEOSFrechetDistanceDensify_r(ctxt_, geom2_, geom1_, 0.5, &dist), 0); + + useCurveConversion(); + ensure_equals(GEOSFrechetDistance_r(ctxt_, geom1_, geom2_, &dist), 1); + ensure_equals(GEOSFrechetDistance_r(ctxt_, geom2_, geom1_, &dist), 1); + ensure_equals(GEOSFrechetDistanceDensify_r(ctxt_, geom1_, geom2_, 0.5, &dist), 1); + ensure_equals(GEOSFrechetDistanceDensify_r(ctxt_, geom2_, geom1_, 0.5, &dist), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSGetCentroidTest.cpp b/tests/unit/capi/GEOSGetCentroidTest.cpp index 2e9b17b5c5..7d6442734c 100644 --- a/tests/unit/capi/GEOSGetCentroidTest.cpp +++ b/tests/unit/capi/GEOSGetCentroidTest.cpp @@ -138,11 +138,19 @@ template<> template<> void object::test<6>() { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); ensure(input_ != nullptr); - result_ = GEOSGetCentroid(input_); + result_ = GEOSGetCentroid_r(ctxt_, input_); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSGetCentroid_r(ctxt_, input_); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POINT); } template<> diff --git a/tests/unit/capi/GEOSGridIntersectionFractionsTest.cpp b/tests/unit/capi/GEOSGridIntersectionFractionsTest.cpp index 412f963771..5dacbf1758 100644 --- a/tests/unit/capi/GEOSGridIntersectionFractionsTest.cpp +++ b/tests/unit/capi/GEOSGridIntersectionFractionsTest.cpp @@ -38,12 +38,17 @@ template<> void object::test<2>() { set_test_name("curved input"); + useContext(); input_ = fromWKT("CURVEPOLYGON ((0.5 0.5, 2.5 0.5, 2.5 2.5, 0.5 2.5, 0.5 0.5))"); - std::vector result_vec; - int result = GEOSGridIntersectionFractions(input_, 1, 0, 5, 3, 4, 3, result_vec.data()); + std::vector result_vec(12); + int result = GEOSGridIntersectionFractions_r(ctxt_, input_, 1, 0, 5, 3, 4, 3, result_vec.data()); ensure_equals(result, 0); + + useCurveConversion(); + result = GEOSGridIntersectionFractions_r(ctxt_, input_, 1, 0, 5, 3, 4, 3, result_vec.data()); + ensure_equals(result, 1); } template<> diff --git a/tests/unit/capi/GEOSHausdorffDistanceTest.cpp b/tests/unit/capi/GEOSHausdorffDistanceTest.cpp index 747d2a9df8..db298ccb14 100644 --- a/tests/unit/capi/GEOSHausdorffDistanceTest.cpp +++ b/tests/unit/capi/GEOSHausdorffDistanceTest.cpp @@ -61,6 +61,10 @@ template<> template<> void object::test<3>() { + set_test_name("curved inputs"); + + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 2, 2 2)"); @@ -68,8 +72,29 @@ void object::test<3>() ensure(geom2_); double dist; - ensure_equals("curved geometry not supported", GEOSHausdorffDistance(geom1_, geom2_, &dist), 0); - ensure_equals("curved geometry not supported", GEOSHausdorffDistance(geom2_, geom1_, &dist), 0); + double p1x, p1y, p2x, p2y; + + ensure_equals(GEOSHausdorffDistance_r(ctxt_, geom1_, geom2_, &dist), 0); + ensure_equals(GEOSHausdorffDistance_r(ctxt_, geom2_, geom1_, &dist), 0); + ensure_equals(GEOSHausdorffDistanceDensify_r(ctxt_, geom1_, geom2_, 0.5, &dist), 0); + ensure_equals(GEOSHausdorffDistanceDensify_r(ctxt_, geom2_, geom1_, 0.5, &dist), 0); + + ensure_equals(GEOSHausdorffDistanceWithPoints_r(ctxt_, geom1_, geom2_, &dist, &p1x, &p1y, &p2x, &p2y), 0); + ensure_equals(GEOSHausdorffDistanceWithPoints_r(ctxt_, geom2_, geom1_, &dist, &p1x, &p1y, &p2x, &p2y), 0); + ensure_equals(GEOSHausdorffDistanceDensifyWithPoints_r(ctxt_, geom1_, geom2_, 0.5, &dist, &p1x, &p1y, &p2x, &p2y), 0); + ensure_equals(GEOSHausdorffDistanceDensifyWithPoints_r(ctxt_, geom2_, geom1_, 0.5, &dist, &p1x, &p1y, &p2x, &p2y), 0); + + useCurveConversion(); + ensure_equals(GEOSHausdorffDistance_r(ctxt_, geom1_, geom2_, &dist), 1); + ensure_equals(GEOSHausdorffDistance_r(ctxt_, geom2_, geom1_, &dist), 1); + ensure_equals(GEOSHausdorffDistanceDensify_r(ctxt_, geom1_, geom2_, 0.5, &dist), 1); + ensure_equals(GEOSHausdorffDistanceDensify_r(ctxt_, geom2_, geom1_, 0.5, &dist), 1); + + ensure_equals(GEOSHausdorffDistanceWithPoints_r(ctxt_, geom1_, geom2_, &dist, &p1x, &p1y, &p2x, &p2y), 1); + ensure_equals(GEOSHausdorffDistanceWithPoints_r(ctxt_, geom2_, geom1_, &dist, &p1x, &p1y, &p2x, &p2y), 1); + ensure_equals(GEOSHausdorffDistanceDensifyWithPoints_r(ctxt_, geom1_, geom2_, 0.5, &dist, &p1x, &p1y, &p2x, &p2y), 1); + ensure_equals(GEOSHausdorffDistanceDensifyWithPoints_r(ctxt_, geom2_, geom1_, 0.5, &dist, &p1x, &p1y, &p2x, &p2y), 1); + } template<> diff --git a/tests/unit/capi/GEOSInterpolateTest.cpp b/tests/unit/capi/GEOSInterpolateTest.cpp index 5cfeec7877..4096cd690a 100644 --- a/tests/unit/capi/GEOSInterpolateTest.cpp +++ b/tests/unit/capi/GEOSInterpolateTest.cpp @@ -3,6 +3,7 @@ #include // geos #include +#include #include "capi_test_utils.h" @@ -148,4 +149,35 @@ void object::test<10> ensure(result_ == nullptr); } +template<> +template<> +void object::test<11>() +{ + set_test_name("CircularString input"); + useContext(); + + input_ = fromWKT("CIRCULARSTRING (0 0, 2 2, 4 0)"); + ensure(input_); + + result_ = GEOSInterpolate_r(ctxt_, input_, geos::MATH_PI); + ensure(!result_); + + result_ = GEOSInterpolateNormalized_r(ctxt_, input_, 0.5); + ensure(!result_); + + useCurveConversion(); + result_ = GEOSInterpolate_r(ctxt_, input_, geos::MATH_PI); + ensure(result_); + + expected_ = fromWKT("POINT (2 2)"); + ensure(expected_); + + ensure_geometry_equals_exact(result_, expected_, 0.1); + + geom3_ = GEOSInterpolateNormalized_r(ctxt_, input_, 0.5); + ensure(geom3_); + + ensure_geometry_equals_exact(geom3_, expected_, 0.1); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSIntersectionTest.cpp b/tests/unit/capi/GEOSIntersectionTest.cpp index 4a03845df4..7beb87e1d3 100644 --- a/tests/unit/capi/GEOSIntersectionTest.cpp +++ b/tests/unit/capi/GEOSIntersectionTest.cpp @@ -149,14 +149,24 @@ template<> template<> void object::test<8>() { + set_test_name("curved inputs, non-curved output"); + + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); ensure(geom1_); ensure(geom2_); - result_ = GEOSIntersection(geom1_, geom2_); + result_ = GEOSIntersection_r(ctxt_, geom1_, geom2_); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSIntersection_r(ctxt_, geom1_, geom2_); + expected_ = fromWKT("POINT (1.7067836774 0.7067836774)"); + + ensure_geometry_equals(result_, expected_, 1e-6); } // https://github.com/libgeos/geos/issues/1074 @@ -175,5 +185,41 @@ void object::test<9>() ensure("HasM", GEOSHasM(result_)); } +template<> +template<> +void object::test<10>() +{ + set_test_name("curved inputs, curved output"); + + useContext(); + + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"); + + ensure(geom1_); + ensure(geom2_); + GEOSSetSRID_r(ctxt_, geom1_, 4326); + + result_ = GEOSIntersection_r(ctxt_, geom1_, geom2_); + ensure("curved geometry not supported", result_ == nullptr); + + GEOSCurveToLineParams_setTolerance_r(ctxt_, curveToLineParams_, GEOS_CURVETOLINE_STEP_DEGREES, 1); + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + + // Input converted to line, output not converted to curve + result_ = GEOSIntersection_r(ctxt_, geom1_, geom2_); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_LINESTRING); + GEOSGeom_destroy_r(ctxt_, result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSIntersection_r(ctxt_, geom1_, geom2_); + expected_ = fromWKT("CIRCULARSTRING (0 0, 0.292893 0.707107, 1 1)"); + ensure_geometry_equals(result_, expected_, 1e-6); + + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSIntersectsTest.cpp b/tests/unit/capi/GEOSIntersectsTest.cpp index fe11fd70e5..f11ac8615d 100644 --- a/tests/unit/capi/GEOSIntersectsTest.cpp +++ b/tests/unit/capi/GEOSIntersectsTest.cpp @@ -256,5 +256,21 @@ void object::test<13>() ensure_equals(GEOSIntersects(geom2_, geom1_), 0); } +template<> +template<> +void object::test<14>() +{ + set_test_name("GEOSIntersects with automatic linearization"); + + useContext(); + + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("CIRCULARSTRING (2 0, 3 -1, 4 0)"); + + ensure_equals(2, GEOSIntersects_r(ctxt_, geom1_, geom2_)); + useCurveConversion(); + ensure_equals(1, GEOSIntersects_r(ctxt_, geom1_, geom2_)); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSLargestEmptyCircleTest.cpp b/tests/unit/capi/GEOSLargestEmptyCircleTest.cpp index c47dbbb7db..5d5531b90f 100644 --- a/tests/unit/capi/GEOSLargestEmptyCircleTest.cpp +++ b/tests/unit/capi/GEOSLargestEmptyCircleTest.cpp @@ -64,26 +64,22 @@ template<> template<> void object::test<3>() { - input_ = fromWKT("MULTICURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (0 3, 2 3))"); - ensure(input_); - - result_ = GEOSLargestEmptyCircle(input_, nullptr, 0.001); + set_test_name("curved inputs"); + useContext(); - ensure("curved geometries not supported", result_ == nullptr); -} - -template<> -template<> -void object::test<4>() -{ - input_ = GEOSGeomFromWKT("MULTILINESTRING ((40 90, 90 60), (90 40, 40 10))"); - geom2_ = GEOSGeomFromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING(0 100, 50 150, 100 100), (100 100, 100 0, 0 0, 0 100)))"); + input_ = fromWKT("MULTILINESTRING ((40 90, 90 60), (90 40, 40 10))"); + geom2_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING(0 100, 50 150, 100 100), (100 100, 100 0, 0 0, 0 100)))"); ensure(input_); ensure(geom2_); - result_ = GEOSLargestEmptyCircle(input_, geom2_, 0.001); - + result_ = GEOSLargestEmptyCircle_r(ctxt_, input_, geom2_, 0.001); ensure("curved geometries not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSLargestEmptyCircle_r(ctxt_, input_, geom2_, 0.001); + ensure(result_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_LINESTRING); } diff --git a/tests/unit/capi/GEOSLineToCurveTest.cpp b/tests/unit/capi/GEOSLineToCurveTest.cpp new file mode 100644 index 0000000000..55fdaa0df4 --- /dev/null +++ b/tests/unit/capi/GEOSLineToCurveTest.cpp @@ -0,0 +1,89 @@ +#include +// geos +#include +// std +#include + +#include "capi_test_utils.h" + +namespace tut { + +// +// Test Group +// +struct test_capigeoslinetocurve_data : public capitest::utility { + test_capigeoslinetocurve_data() { + params_ = GEOSLineToCurveParams_create(); + } + + ~test_capigeoslinetocurve_data() override { + GEOSLineToCurveParams_destroy(params_); + } + + GEOSLineToCurveParams* params_; +}; + +using group = test_group; +using object = group::object; + +group test_capigeoslinetocurve_group("capi::GEOSLineToCurve"); + +// +// Test Cases +// + +template<> +template<> +void object::test<1>() +{ + set_test_name("empty input preserved"); + + input_ = fromWKT("LINESTRING EMPTY"); + result_ = GEOSLineToCurve(input_, params_); + ensure_equals(GEOSGeomTypeId(result_), GEOS_LINESTRING); + ensure(GEOSisEmpty(result_)); +} + +template<> +template<> +void object::test<2>() +{ + set_test_name("curved input copied"); + + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + result_ = GEOSLineToCurve(input_, params_); + + ensure_geometry_equals_identical(result_, input_); +} + + +template<> +template<> +void object::test<3>() +{ + set_test_name("LineString to CircularString, non-default conversion parameters"); + + input_ = fromWKT("LINESTRING(0 0,13.3975 50,50 86.6025,100 100,150 86.6025,186.6025 50,200 0)"); + + ensure(GEOSLineToCurveParams_setRadiusTolerance(params_, 1e-5)); + + result_ = GEOSLineToCurve(input_, params_); + expected_ = fromWKT("CIRCULARSTRING (0 0, 100 100, 200 0)"); + + ensure_geometry_equals_exact(result_, expected_, 1e-3); + + // max step angle too small to allow conversion + GEOSGeom_destroy(result_); + ensure(GEOSLineToCurveParams_setMaxStepDegrees(params_, 10)); + result_ = GEOSLineToCurve(input_, params_); + ensure_geometry_equals_identical(result_, input_); + + // max angle difference too small to allow conversion + GEOSGeom_destroy(result_); + ensure(GEOSLineToCurveParams_setMaxStepDegrees(params_, 90)); + ensure(GEOSLineToCurveParams_setMaxAngleDifferenceDegrees(params_, 1e-14)); + result_ = GEOSLineToCurve(input_, params_); + ensure_geometry_equals_identical(result_, input_); +} + +} // namespace tut diff --git a/tests/unit/capi/GEOSMakeValidTest.cpp b/tests/unit/capi/GEOSMakeValidTest.cpp index 0cad2e5521..4aa78774c4 100644 --- a/tests/unit/capi/GEOSMakeValidTest.cpp +++ b/tests/unit/capi/GEOSMakeValidTest.cpp @@ -3,6 +3,7 @@ #include // geos #include +#include // std #include #include @@ -94,12 +95,39 @@ template<> template<> void object::test<5>() { + set_test_name("curved inputs, curved output"); + useContext(); + // ring outside shell input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)), (10 10, 30 10, 30 30, 10 10))"); ensure(input_); + GEOSSetSRID_r(ctxt_, input_, 4326); - result_ = GEOSMakeValid(input_); + + result_ = GEOSMakeValid_r(ctxt_, input_); ensure("curved geometry not supported", result_ == nullptr); + + // Input converted to line, output not converted to curve + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + result_ = GEOSMakeValid_r(ctxt_, input_); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTIPOLYGON); + GEOSGeom_destroy_r(ctxt_, result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSMakeValid_r(ctxt_, input_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTISURFACE); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 2); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0u)), GEOS_CURVEPOLYGON); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 1u)), GEOS_POLYGON); + + double area = -1; + ensure(GEOSArea_r(ctxt_, result_, &area)); + ensure_equals("area does not match expected", area, geos::MATH_PI*10*10/2 + 20*20/2, 1e-2); + + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); } template<> diff --git a/tests/unit/capi/GEOSMaximumInscribedCircleTest.cpp b/tests/unit/capi/GEOSMaximumInscribedCircleTest.cpp index c309d0a76a..522cdc19cb 100644 --- a/tests/unit/capi/GEOSMaximumInscribedCircleTest.cpp +++ b/tests/unit/capi/GEOSMaximumInscribedCircleTest.cpp @@ -23,9 +23,13 @@ struct test_capimaximuminscribedcircle_data : public capitest::utility { GEOSWKTWriter_setRoundingPrecision(wktw_, 8); } - ~test_capimaximuminscribedcircle_data() + ~test_capimaximuminscribedcircle_data() override { - GEOSFree(wkt_); + if (ctxt_) { + GEOSFree_r(ctxt_, wkt_); + } else { + GEOSFree(wkt_); + } } }; @@ -74,11 +78,20 @@ template<> template<> void object::test<3>() { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)))"); ensure(input_); - result_ = GEOSMaximumInscribedCircle(input_, 1); + result_ = GEOSMaximumInscribedCircle_r(ctxt_, input_, 1); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSMaximumInscribedCircle_r(ctxt_, input_, 1); + ensure(result_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_LINESTRING); } } // namespace tut diff --git a/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp b/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp index 3634752e9c..144be2ae37 100644 --- a/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp +++ b/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp @@ -120,13 +120,21 @@ template<> void object::test<5> () { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(input_); - GEOSGeometry* center; double radius; - result_ = GEOSMinimumBoundingCircle(input_, &radius, ¢er); + result_ = GEOSMinimumBoundingCircle_r(ctxt_, input_, &radius, &geom1_); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSMinimumBoundingCircle_r(ctxt_, input_, &radius, &geom1_); + ensure(result_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); } template<> diff --git a/tests/unit/capi/GEOSMinimumClearanceTest.cpp b/tests/unit/capi/GEOSMinimumClearanceTest.cpp index 49fca2637a..4c9dbe699a 100644 --- a/tests/unit/capi/GEOSMinimumClearanceTest.cpp +++ b/tests/unit/capi/GEOSMinimumClearanceTest.cpp @@ -143,4 +143,31 @@ void object::test<7>() ensure_geometry_equals(result_, expected_); } +template<> +template<> +void object::test<8>() +{ + set_test_name("curved inputs"); + useContext(); + + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 5 5, 10 0), (10 0, 15 0, 15 10, 10 10), CIRCULARSTRING (10 10, 5 5.1, 0 10), (0 10, -5 10, -5 0, 0 0)))"); + ensure(input_); + + double result = -1; + ensure_equals(GEOSMinimumClearance_r(ctxt_, input_, &result), 2); + ensure(GEOSMinimumClearanceLine_r(ctxt_, input_) == nullptr); + + useCurveConversion(); + ensure_equals(GEOSMinimumClearance_r(ctxt_, input_, &result), 0); + ensure_equals("minimum clearance value does not match", result, 0.1, 0.01); + + result_ = GEOSMinimumClearanceLine_r(ctxt_, input_); + ensure(result_); + + expected_ = fromWKT("LINESTRING (5 5, 5 5.1)"); + ensure(expected_); + + ensure_geometry_equals(result_, expected_, 0.2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSMinimumRotatedRectangleTest.cpp b/tests/unit/capi/GEOSMinimumRotatedRectangleTest.cpp index b2eff83fd9..55ffc166c6 100644 --- a/tests/unit/capi/GEOSMinimumRotatedRectangleTest.cpp +++ b/tests/unit/capi/GEOSMinimumRotatedRectangleTest.cpp @@ -123,11 +123,20 @@ template<> void object::test<8> () { - input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + set_test_name("curved inputs"); + useContext(); + + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1.5, 2 1)"); ensure(input_); - result_ = GEOSMinimumRotatedRectangle(input_); + result_ = GEOSMinimumRotatedRectangle_r(ctxt_, input_); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSMinimumRotatedRectangle_r(ctxt_, input_); + + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); } } // namespace tut diff --git a/tests/unit/capi/GEOSMinimumWidthTest.cpp b/tests/unit/capi/GEOSMinimumWidthTest.cpp index 4b5658126f..bb10e5ed8b 100644 --- a/tests/unit/capi/GEOSMinimumWidthTest.cpp +++ b/tests/unit/capi/GEOSMinimumWidthTest.cpp @@ -68,11 +68,19 @@ template<> void object::test<3> () { - input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + set_test_name("curved inputs"); + useContext(); + + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1.5, 2 1)"); ensure(input_); - result_ = GEOSMinimumWidth(input_); + result_ = GEOSMinimumWidth_r(ctxt_, input_); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSMinimumWidth_r(ctxt_, input_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_LINESTRING); } } // namespace tut diff --git a/tests/unit/capi/GEOSNearestPointsTest.cpp b/tests/unit/capi/GEOSNearestPointsTest.cpp index fb75817517..1b9f9a2826 100644 --- a/tests/unit/capi/GEOSNearestPointsTest.cpp +++ b/tests/unit/capi/GEOSNearestPointsTest.cpp @@ -81,6 +81,8 @@ template<> void object::test<2> () { + set_test_name("closest points are vertices of two disjoint inputs"); + checkNearestPoints( "POLYGON((1 1,1 5,5 5,5 1,1 1))", "POLYGON((8 8, 9 9, 9 10, 8 8))", @@ -94,6 +96,8 @@ template<> void object::test<3> () { + set_test_name("point inside polygon"); + checkNearestPoints( "POLYGON((1 1,1 5,5 5,5 1,1 1))", "POINT(2 2)", @@ -107,6 +111,8 @@ template<> void object::test<4> () { + set_test_name("closest point is not a vertex of LineString"); + checkNearestPoints( "LINESTRING(1 5,5 5,5 1,1 1)", "POINT(2 2)", @@ -119,6 +125,8 @@ template<> void object::test<5> () { + set_test_name("two crossing LineStrings"); + checkNearestPoints( "LINESTRING(0 0,10 10)", "LINESTRING(0 10,10 0)", @@ -131,6 +139,8 @@ template<> void object::test<6> () { + set_test_name("LineString partially inside Polygon"); + checkNearestPoints( "POLYGON((0 0,10 0,10 10,0 10,0 0))", "LINESTRING(8 5,12 5)", @@ -141,8 +151,7 @@ void object::test<6> template<> template<> -void object::test<7>() -{ +void object::test<7>() { set_test_name("2D points returned for 4D inputs"); geom1_ = fromWKT("POINT ZM (0 0 1 2)"); @@ -159,5 +168,41 @@ void object::test<7>() GEOSCoordSeq_destroy(coords); } +template<> +template<> +void object::test<8> +() +{ + set_test_name("curved inputs"); + + useContext(); + + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (3 0, 4 0)"); + + GEOSCoordSequence* coords = GEOSNearestPoints_r(ctxt_, geom1_, geom2_); + ensure(coords == nullptr); + + useCurveConversion(); + + coords = GEOSNearestPoints_r(ctxt_, geom1_, geom2_); + ensure(coords != nullptr); + + unsigned int size; + ensure(GEOSCoordSeq_getSize_r(ctxt_, coords, &size)); + ensure_equals(size, 2u); + + double x1, y1, x2, y2; + GEOSCoordSeq_getXY_r(ctxt_, coords, 0, &x1, &y1); + GEOSCoordSeq_getXY_r(ctxt_, coords, 1, &x2, &y2); + + ensure_equals(x1, 2); + ensure_equals(y1, 0); + ensure_equals(x2, 3); + ensure_equals(y2, 0); + + GEOSCoordSeq_destroy_r(ctxt_, coords); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSOffsetCurveTest.cpp b/tests/unit/capi/GEOSOffsetCurveTest.cpp index 242d188530..799546ddc0 100644 --- a/tests/unit/capi/GEOSOffsetCurveTest.cpp +++ b/tests/unit/capi/GEOSOffsetCurveTest.cpp @@ -262,12 +262,19 @@ template<> template<> void object::test<13>() { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(input_); - result_ = GEOSOffsetCurve(input_, 1, 8, GEOSBUF_JOIN_ROUND, 0); - + result_ = GEOSOffsetCurve_r(ctxt_, input_, 1, 8, GEOSBUF_JOIN_ROUND, 0); ensure("curved geometries not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSOffsetCurve_r(ctxt_, input_, 1, 8, GEOSBUF_JOIN_ROUND, 0); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_LINESTRING); } template<> diff --git a/tests/unit/capi/GEOSOverlapsTest.cpp b/tests/unit/capi/GEOSOverlapsTest.cpp index 253bd762f3..7298fe1dcd 100644 --- a/tests/unit/capi/GEOSOverlapsTest.cpp +++ b/tests/unit/capi/GEOSOverlapsTest.cpp @@ -39,14 +39,20 @@ template<> template<> void object::test<2>() { - geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); - geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + set_test_name("GEOSOverlaps with automatic linearization"); + useContext(); + + geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + geom2_ = fromWKT("POLYGON ((1 0, 2 0, 2 1, 1 0 ))"); ensure(geom1_); ensure(geom2_); - ensure_equals("curved geometry not supported", GEOSOverlaps(geom1_, geom2_), 2); - ensure_equals("curved geometry not supported", GEOSOverlaps(geom2_, geom1_), 2); + ensure_equals(GEOSOverlaps_r(ctxt_, geom1_, geom2_), 2); + ensure_equals(GEOSOverlaps_r(ctxt_, geom2_, geom1_), 2); + useCurveConversion(); + ensure_equals(GEOSOverlaps_r(ctxt_, geom1_, geom2_), 1); + ensure_equals(GEOSOverlaps_r(ctxt_, geom2_, geom1_), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSPointOnSurfaceTest.cpp b/tests/unit/capi/GEOSPointOnSurfaceTest.cpp index de1ab2d3c4..8afef2a3b1 100644 --- a/tests/unit/capi/GEOSPointOnSurfaceTest.cpp +++ b/tests/unit/capi/GEOSPointOnSurfaceTest.cpp @@ -224,11 +224,20 @@ template<> void object::test<10> () { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); ensure(input_); - result_ = GEOSPointOnSurface(input_); + result_ = GEOSPointOnSurface_r(ctxt_, input_); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSPointOnSurface_r(ctxt_, input_); + ensure(result_); + + ensure_equals(GEOSContains_r(ctxt_, input_, result_), 1); } template<> diff --git a/tests/unit/capi/GEOSPolygonHullSimplifyTest.cpp b/tests/unit/capi/GEOSPolygonHullSimplifyTest.cpp index f769b001fa..d126995bcf 100644 --- a/tests/unit/capi/GEOSPolygonHullSimplifyTest.cpp +++ b/tests/unit/capi/GEOSPolygonHullSimplifyTest.cpp @@ -93,13 +93,43 @@ template<> template<> void object::test<6>() { + set_test_name("GEOSPolygonHullSimplify curved inputs"); + useContext(); + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 1 0.0001, 0 0)))"); ensure(input_); - result_ = GEOSPolygonHullSimplify(input_, 1, 0.8); + result_ = GEOSPolygonHullSimplify_r(ctxt_, input_, 1, 0.8); ensure("curved geometries not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSPolygonHullSimplify_r(ctxt_, input_, 1, 0.8); + ensure(result_); + + expected_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE ((0 0, 2 0), CIRCULARSTRING (2 0, 1 1, 0 0)))"); + ensure_geometry_equals(result_, expected_); } +template<> +template<> +void object::test<7>() +{ + set_test_name("GEOSPolygonHullSimplifyMode curved inputs"); + useContext(); + + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 1 0.0001, 0 0)))"); + ensure(input_); + + result_ = GEOSPolygonHullSimplifyMode_r(ctxt_, input_, 1, GEOSHULL_PARAM_AREA_RATIO, 0.8); + ensure("curved geometries not supported", result_ == nullptr); + + useCurveConversion(); + result_ = GEOSPolygonHullSimplifyMode_r(ctxt_, input_, 1, GEOSHULL_PARAM_AREA_RATIO, 0.8); + ensure(result_); + + expected_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE ((0 0, 2 0), CIRCULARSTRING (2 0, 1 1, 0 0)))"); + ensure_geometry_equals(result_, expected_); +} } // namespace tut diff --git a/tests/unit/capi/GEOSPolygonizeTest.cpp b/tests/unit/capi/GEOSPolygonizeTest.cpp index 145743cccb..7511083e93 100644 --- a/tests/unit/capi/GEOSPolygonizeTest.cpp +++ b/tests/unit/capi/GEOSPolygonizeTest.cpp @@ -189,45 +189,138 @@ template<> void object::test<7> () { - constexpr int size = 2; - GEOSGeometry* geoms[size]; - geoms[0] = GEOSGeomFromWKT("LINESTRING (0 0, 2 0)"); - geoms[1] = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + set_test_name("LINESTRING ZM inputs"); - for (auto& geom : geoms) { - ensure(geom != nullptr); - } + geom1_ = fromWKT("LINESTRING ZM (0 0 5 4, 2 0 6 5, 2 2 7 6)"); + geom2_ = fromWKT("LINESTRING ZM (2 2 7 6, 0 0 5 4)"); - GEOSGeometry* g = GEOSPolygonize(geoms, size); + ensure(geom1_); + ensure(geom2_); - ensure("curved geometries not supported", g == nullptr); + std::array geoms = {geom1_, geom2_}; + result_ = GEOSPolygonize(geoms.data(), geoms.size()); + ensure(result_); - for(auto& input : geoms) { - GEOSGeom_destroy(input); - } + expected_ = fromWKT("GEOMETRYCOLLECTION ZM (POLYGON ZM ((2 2 7 6, 2 0 6 5, 0 0 5 4, 2 2 7 6)))"); + ensure(expected_); + + ensure_geometry_equals_identical(result_, expected_); } + template<> template<> void object::test<8> () { - set_test_name("LINESTRING ZM inputs"); + set_test_name("GEOSPolygonize curved inputs"); + useContext(); - geom1_ = fromWKT("LINESTRING ZM (0 0 5 4, 2 0 6 5, 2 2 7 6)"); - geom2_ = fromWKT("LINESTRING ZM (2 2 7 6, 0 0 5 4)"); + geom1_ = fromWKT("LINESTRING (0 0, 2 0)"); + geom2_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); - ensure(geom1_); - ensure(geom2_); + constexpr int size = 2; + std::array geoms{geom1_, geom2_}; - std::array geoms = {geom1_, geom2_}; - result_ = GEOSPolygonize(geoms.data(), geoms.size()); + result_ = GEOSPolygonize_r(ctxt_, geoms.data(), size); + ensure(!result_); + + // Input converted to line, output not converted to curve + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + result_ = GEOSPolygonize_r(ctxt_, geoms.data(), size); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_GEOMETRYCOLLECTION); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 1); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0)), GEOS_POLYGON); + GEOSGeom_destroy(result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSPolygonize_r(ctxt_, geoms.data(), size); ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_GEOMETRYCOLLECTION); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 1); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0)), GEOS_CURVEPOLYGON); +} - expected_ = fromWKT("GEOMETRYCOLLECTION ZM (POLYGON ZM ((2 2 7 6, 2 0 6 5, 0 0 5 4, 2 2 7 6)))"); - ensure(expected_); +template<> +template<> +void object::test<9> +() +{ + set_test_name("GEOSPolygonize_valid curved inputs returning a single polygon"); + useContext(); - ensure_geometry_equals_identical(result_, expected_); + geom1_ = fromWKT("LINESTRING (0 0, 20 0)"); + geom2_ = fromWKT("CIRCULARSTRING (0 0, 10 10, 20 0)"); + geom3_ = fromWKT("LINESTRING (9 1, 11 1, 11 2, 9 2, 9 1)"); + GEOSSetSRID_r(ctxt_, geom3_, 4326); + + constexpr int size = 3; + std::array geoms{geom1_, geom2_, geom3_}; + + result_ = GEOSPolygonize_valid_r(ctxt_, geoms.data(), size); + ensure(!result_); + + // Input converted to line, output not converted to curve + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + result_ = GEOSPolygonize_valid_r(ctxt_, geoms.data(), size); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 1); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0)), GEOS_POLYGON); + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); + GEOSGeom_destroy(result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSPolygonize_valid_r(ctxt_, geoms.data(), size); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_CURVEPOLYGON); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 1); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0)), GEOS_CURVEPOLYGON); + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); +} + +template<> +template<> +void object::test<10> +() +{ + set_test_name("GEOSPolygonize_valid curved inputs returning a multiple polygons"); + useContext(); + + geom1_ = fromWKT("LINESTRING (0 0, 20 0)"); + geom2_ = fromWKT("CIRCULARSTRING (0 0, 10 10, 20 0)"); + geom3_ = fromWKT("LINESTRING (20 0, 30 0, 30 30, 20 0)"); + GEOSSetSRID_r(ctxt_, geom3_, 4326); + + constexpr int size = 3; + std::array geoms{geom1_, geom2_, geom3_}; + + result_ = GEOSPolygonize_valid_r(ctxt_, geoms.data(), size); + ensure(!result_); + + // Input converted to line, output not converted to curve + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + result_ = GEOSPolygonize_valid_r(ctxt_, geoms.data(), size); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTIPOLYGON); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 2); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0)), GEOS_POLYGON); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 1)), GEOS_POLYGON); + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); + GEOSGeom_destroy(result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSPolygonize_valid_r(ctxt_, geoms.data(), size); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTISURFACE); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 2); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0)), GEOS_CURVEPOLYGON); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 1)), GEOS_POLYGON); + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); } } // namespace tut diff --git a/tests/unit/capi/GEOSProjectTest.cpp b/tests/unit/capi/GEOSProjectTest.cpp index d2556a8979..a8f550809c 100644 --- a/tests/unit/capi/GEOSProjectTest.cpp +++ b/tests/unit/capi/GEOSProjectTest.cpp @@ -3,6 +3,7 @@ #include // geos #include +#include #include "capi_test_utils.h" @@ -119,4 +120,27 @@ void object::test<5> ensure_equals("GEOSProjectNormalized", dist_norm, 0.25); } +template<> +template<> +void object::test<6> +() +{ + set_test_name("CircularString input"); + useContext(); + + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("POINT (1 1.1)"); + + ensure_equals(GEOSProject_r(ctxt_, geom1_, geom2_), -1.0); + ensure_equals(GEOSProjectNormalized_r(ctxt_, geom1_, geom2_), -1.0); + + useCurveConversion(); + + double dist = GEOSProject_r(ctxt_, geom1_, geom2_); + ensure_equals("GEOSProject result does not match", dist, geos::MATH_PI/2, 1e-2); + + double dist_norm = GEOSProjectNormalized_r(ctxt_, geom1_, geom2_); + ensure_equals("GEOSProjectNormalized result does not match", dist_norm, 0.5, 1e-3); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSRelateBoundaryNodeRuleTest.cpp b/tests/unit/capi/GEOSRelateBoundaryNodeRuleTest.cpp index d27d4d23b8..4a60bb9480 100644 --- a/tests/unit/capi/GEOSRelateBoundaryNodeRuleTest.cpp +++ b/tests/unit/capi/GEOSRelateBoundaryNodeRuleTest.cpp @@ -21,7 +21,9 @@ struct test_capigeosrelateboundarynoderule_data : public capitest::utility { ~test_capigeosrelateboundarynoderule_data() { - GEOSFree(pat_); + if (pat_) { + GEOSFree(pat_); + } } }; @@ -136,6 +138,29 @@ void object::test<8> ensure(nullptr == pat_); } +template<> +template<> +void object::test<9>() +{ + set_test_name("GEOSRelateBoundaryNodeRule with automatic linearization"); + + useContext(); + + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + ensure(GEOSRelateBoundaryNodeRule_r(ctxt_, geom1_, geom2_, GEOSRELATE_BNR_MOD2) == nullptr); + ensure(GEOSRelateBoundaryNodeRule_r(ctxt_, geom2_, geom1_, GEOSRELATE_BNR_MOD2) == nullptr); + + useCurveConversion(); + + char* pattern = GEOSRelateBoundaryNodeRule_r(ctxt_, geom1_, geom2_, GEOSRELATE_BNR_MOD2); + ensure(pattern); + GEOSFree_r(ctxt_, pattern); +} } // namespace tut diff --git a/tests/unit/capi/GEOSRelatePatternTest.cpp b/tests/unit/capi/GEOSRelatePatternTest.cpp index cfd1bfe192..d36002432b 100644 --- a/tests/unit/capi/GEOSRelatePatternTest.cpp +++ b/tests/unit/capi/GEOSRelatePatternTest.cpp @@ -35,14 +35,23 @@ template<> template<> void object::test<2>() { + set_test_name("GEOSRelatePattern with automatic linearization"); + + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); ensure(geom1_); ensure(geom2_); - ensure_equals("curved geometry not supported", GEOSRelatePattern(geom1_, geom2_, "0********"), 2); - ensure_equals("curved geometry not supported", GEOSRelatePattern(geom2_, geom1_, "0********"), 2); + ensure_equals(GEOSRelatePattern_r(ctxt_, geom1_, geom2_, "0********"), 2); + ensure_equals(GEOSRelatePattern_r(ctxt_, geom2_, geom1_, "0********"), 2); + + useCurveConversion(); + + ensure_equals(GEOSRelatePattern_r(ctxt_, geom1_, geom2_, "0********"), 1); + ensure_equals(GEOSRelatePattern_r(ctxt_, geom2_, geom1_, "0********"), 1); } // invalid DE-9IM diff --git a/tests/unit/capi/GEOSRelateTest.cpp b/tests/unit/capi/GEOSRelateTest.cpp index f531ee4ab3..ff03684ae7 100644 --- a/tests/unit/capi/GEOSRelateTest.cpp +++ b/tests/unit/capi/GEOSRelateTest.cpp @@ -34,14 +34,24 @@ template<> template<> void object::test<2>() { + set_test_name("GEOSRelate with automatic linearization"); + + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); ensure(geom1_); ensure(geom2_); - ensure("curved geometry not supported", GEOSRelate(geom1_, geom2_) == nullptr); - ensure("curved geometry not supported", GEOSRelate(geom2_, geom1_) == nullptr); + ensure(GEOSRelate_r(ctxt_, geom1_, geom2_) == nullptr); + ensure(GEOSRelate_r(ctxt_, geom2_, geom1_) == nullptr); + + useCurveConversion(); + + char* pattern = GEOSRelate_r(ctxt_, geom1_, geom2_); + ensure(pattern); + GEOSFree_r(ctxt_, pattern); } } // namespace tut diff --git a/tests/unit/capi/GEOSSharedPathsTest.cpp b/tests/unit/capi/GEOSSharedPathsTest.cpp index 4316500ed8..de71641cef 100644 --- a/tests/unit/capi/GEOSSharedPathsTest.cpp +++ b/tests/unit/capi/GEOSSharedPathsTest.cpp @@ -89,14 +89,25 @@ template<> template<> void object::test<4>() { + set_test_name("curved inputs"); + useContext(); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); - geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + geom2_ = fromWKT("COMPOUNDCURVE ((4 0, 2 0), CIRCULARSTRING (2 0, 1 1, 0 0))"); ensure(geom1_); ensure(geom2_); - result_ = GEOSSharedPaths(geom1_, geom2_); + result_ = GEOSSharedPaths_r(ctxt_, geom1_, geom2_); ensure("curved geometry not supported", result_ == nullptr); + + useCurveConversion(); + + result_ = GEOSSharedPaths_r(ctxt_, geom1_, geom2_); + ensure(result_); + + // result remains linear because segments are represented as + // a MultiLineString, not a LineString. } template<> diff --git a/tests/unit/capi/GEOSSubdivideByGridTest.cpp b/tests/unit/capi/GEOSSubdivideByGridTest.cpp index d734c5bcab..809e1b16c2 100644 --- a/tests/unit/capi/GEOSSubdivideByGridTest.cpp +++ b/tests/unit/capi/GEOSSubdivideByGridTest.cpp @@ -53,4 +53,27 @@ void object::test<2>() ensure_geometry_equals_identical(expected_, result_); } +template<> +template<> +void object::test<3>() +{ + set_test_name("curved inputs"); + useContext(); + + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 2 0), (2 0, 0 0)))"); + ensure(input_); + + result_ = GEOSSubdivideByGrid_r(ctxt_, input_, 0, 0, 2, 1, 2, 1, false); + ensure(!result_); + + useCurveConversion(); + result_ = GEOSSubdivideByGrid_r(ctxt_, input_, 0, 0, 2, 1, 2, 1, false); + ensure(result_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_GEOMETRYCOLLECTION); + ensure_equals(GEOSGetNumGeometries_r(ctxt_, result_), 2); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 0)), GEOS_CURVEPOLYGON); + ensure_equals(GEOSGeomTypeId_r(ctxt_, GEOSGetGeometryN_r(ctxt_, result_, 1)), GEOS_CURVEPOLYGON); +} + } \ No newline at end of file diff --git a/tests/unit/capi/GEOSSymDifferenceTest.cpp b/tests/unit/capi/GEOSSymDifferenceTest.cpp index def092b684..6598c7313a 100644 --- a/tests/unit/capi/GEOSSymDifferenceTest.cpp +++ b/tests/unit/capi/GEOSSymDifferenceTest.cpp @@ -33,14 +33,34 @@ template<> template<> void object::test<2>() { - geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); - geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + set_test_name("curved inputs, curved output"); + useContext(); + + geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (-5 0, 0 5, 5 0), (5 0, -5 0)))"); + geom2_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (-5 5, 0 0, 5 5), (5 5, -5 5)))"); ensure(geom1_); ensure(geom2_); + GEOSSetSRID_r(ctxt_, geom1_, 4326); - result_ = GEOSSymDifference(geom1_, geom2_); + result_ = GEOSSymDifference_r(ctxt_, geom1_, geom2_); ensure("curved geometry not supported", result_ == nullptr); + + GEOSCurveToLineParams_setTolerance_r(ctxt_, curveToLineParams_, GEOS_CURVETOLINE_STEP_DEGREES, 1); + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + + // Input converted to line, output not converted to curve + result_ = GEOSSymDifference_r(ctxt_, geom1_, geom2_); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTIPOLYGON); + GEOSGeom_destroy_r(ctxt_, result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSSymDifference_r(ctxt_, geom1_, geom2_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTISURFACE); + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); } } // namespace tut diff --git a/tests/unit/capi/GEOSTouchesTest.cpp b/tests/unit/capi/GEOSTouchesTest.cpp index df6722d5fb..a06c9ad0b2 100644 --- a/tests/unit/capi/GEOSTouchesTest.cpp +++ b/tests/unit/capi/GEOSTouchesTest.cpp @@ -49,5 +49,22 @@ void object::test<2>() ensure_equals("curved geometry not supported", GEOSTouches(geom2_, geom1_), 2); } +template<> +template<> +void object::test<3>() +{ + set_test_name("GEOSTouches with automatic linearization"); + + useContext(); + + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("CIRCULARSTRING (2 0, 3 -1, 4 0)"); + + ensure_equals(2, GEOSTouches_r(ctxt_, geom1_, geom2_)); + + useCurveConversion(); + ensure_equals(1, GEOSTouches_r(ctxt_, geom1_, geom2_)); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSUnaryUnionTest.cpp b/tests/unit/capi/GEOSUnaryUnionTest.cpp index ad431e12b7..c4abb84586 100644 --- a/tests/unit/capi/GEOSUnaryUnionTest.cpp +++ b/tests/unit/capi/GEOSUnaryUnionTest.cpp @@ -219,12 +219,37 @@ template<> template<> void object::test<12>() { - input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("MULTICURVE (CIRCULARSTRING (-5 0, 0 5, 5 0), (-5 3, 5 3))"); ensure(input_); - - result_ = GEOSUnaryUnion(input_); - ensure("curved geometry not supported", result_ == nullptr); + GEOSSetSRID_r(ctxt_, input_, 4326); + double input_length = -1; + ensure(GEOSLength_r(ctxt_, input_, &input_length)); + + result_ = GEOSUnaryUnion_r(ctxt_, input_); + ensure(result_ == nullptr); + + GEOSCurveToLineParams_setTolerance_r(ctxt_, curveToLineParams_, GEOS_CURVETOLINE_STEP_DEGREES, 1); + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + + // Input converted to line, output not converted to curve + result_ = GEOSUnaryUnion_r(ctxt_, input_); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTILINESTRING); + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); + GEOSGeom_destroy_r(ctxt_, result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSUnaryUnion_r(ctxt_, input_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_MULTICURVE); + double result_length = -1; + ensure(GEOSLength_r(ctxt_, result_, &result_length)); + + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); + ensure_equals("length does not match", result_length, input_length, 1e-5); } diff --git a/tests/unit/capi/GEOSUnionTest.cpp b/tests/unit/capi/GEOSUnionTest.cpp index 68c90867b9..b6455614d0 100644 --- a/tests/unit/capi/GEOSUnionTest.cpp +++ b/tests/unit/capi/GEOSUnionTest.cpp @@ -1,6 +1,7 @@ #include // geos #include +#include #include "capi_test_utils.h" @@ -66,14 +67,45 @@ template<> template<> void object::test<3>() { - geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); - geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + set_test_name("curved inputs, curved output"); + useContext(); + + geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (-5 0, 0 5, 5 0), (5 0, -5 0)))"); + geom2_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (-5 0, 0 -5, 5 0), (5 0, -5 0)))"); ensure(geom1_); ensure(geom2_); + GEOSSetSRID_r(ctxt_, geom1_, 4326); - result_ = GEOSUnion(geom1_, geom2_); + result_ = GEOSUnion_r(ctxt_, geom1_, geom2_); ensure("curved geometry not supported", result_ == nullptr); + + GEOSCurveToLineParams_setTolerance_r(ctxt_, curveToLineParams_, GEOS_CURVETOLINE_STEP_DEGREES, 1); + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams_); + + // Input converted to line, output not converted to curve + result_ = GEOSUnion_r(ctxt_, geom1_, geom2_); + ensure(result_); + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_POLYGON); + { + double area = -1; + ensure_equals(GEOSArea_r(ctxt_, result_, &area), 1); + ensure_equals("area does not match expected", area, geos::MATH_PI*5*5, 5e-3); + } + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); + GEOSGeom_destroy_r(ctxt_, result_); + + // Input converted to line, output converted to curve + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams_); + result_ = GEOSUnion_r(ctxt_, geom1_, geom2_); + + ensure_equals(GEOSGeomTypeId_r(ctxt_, result_), GEOS_CURVEPOLYGON); + { + double area = -1; + ensure_equals(GEOSArea_r(ctxt_, result_, &area), 1); + ensure_equals("area does not match expected", area, geos::MATH_PI*5*5, 1e-6); + } + ensure_equals(GEOSGetSRID_r(ctxt_, result_), 4326); } } // namespace tut diff --git a/tests/unit/capi/GEOSVoronoiDiagramTest.cpp b/tests/unit/capi/GEOSVoronoiDiagramTest.cpp index d79181351d..a7785ec01a 100644 --- a/tests/unit/capi/GEOSVoronoiDiagramTest.cpp +++ b/tests/unit/capi/GEOSVoronoiDiagramTest.cpp @@ -183,11 +183,13 @@ template<> void object::test<9> () { + set_test_name("curved inputs"); + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(input_); result_ = GEOSVoronoiDiagram(input_, nullptr, 0, 0); - ensure("curved geometry not supported", result_ == nullptr); + ensure(result_); } } // namespace tut diff --git a/tests/unit/capi/GEOSWithinTest.cpp b/tests/unit/capi/GEOSWithinTest.cpp index 387f409090..42def3e7f2 100644 --- a/tests/unit/capi/GEOSWithinTest.cpp +++ b/tests/unit/capi/GEOSWithinTest.cpp @@ -88,13 +88,19 @@ template<> template<> void object::test<4>() { - geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); - geom2_ = fromWKT("LINESTRING (1 0, 2 0)"); + set_test_name("GEOSWithin with automatic linearization"); + useContext(); + + geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + geom2_ = fromWKT("LINESTRING (1 0.5, 1 0.6)"); ensure(geom1_); ensure(geom2_); - ensure_equals("curved geometry not supported", GEOSWithin(geom2_, geom1_), 2); + ensure_equals(GEOSWithin_r(ctxt_, geom2_, geom1_), 2); + useCurveConversion(); + ensure_equals(GEOSWithin_r(ctxt_, geom1_, geom2_), 0); + ensure_equals(GEOSWithin_r(ctxt_, geom2_, geom1_), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSisRingTest.cpp b/tests/unit/capi/GEOSisRingTest.cpp index 0f263be6cb..25a9c31fd5 100644 --- a/tests/unit/capi/GEOSisRingTest.cpp +++ b/tests/unit/capi/GEOSisRingTest.cpp @@ -80,11 +80,15 @@ template<> void object::test<6> () { - geom1_ = GEOSGeomFromWKT("COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0))"); + set_test_name("curved inputs"); + useContext(); + + geom1_ = fromWKT("COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0))"); ensure(geom1_); - char r = GEOSisRing(geom1_); - ensure_equals("curved geometetries not supported", r, 2); + //ensure_equals(GEOSisRing_r(ctxt_, geom1_), 2); + useCurveConversion(); + ensure_equals(GEOSisRing_r(ctxt_, geom1_), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSisSimpleDetailTest.cpp b/tests/unit/capi/GEOSisSimpleDetailTest.cpp index 95ce756756..f78a42069e 100644 --- a/tests/unit/capi/GEOSisSimpleDetailTest.cpp +++ b/tests/unit/capi/GEOSisSimpleDetailTest.cpp @@ -61,12 +61,15 @@ template<> template<> void object::test<4>() { - set_test_name("error raised on curved geometry"); + set_test_name("curved inputs"); + useContext(); input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(input_ != nullptr); - ensure_equals(GEOSisSimpleDetail(input_, 0, &result_), 2); + ensure_equals(GEOSisSimpleDetail_r(ctxt_, input_, 0, &result_), 2); + useCurveConversion(); + ensure_equals(GEOSisSimpleDetail_r(ctxt_, input_, 0, &result_), 1); } template<> diff --git a/tests/unit/capi/GEOSisSimpleTest.cpp b/tests/unit/capi/GEOSisSimpleTest.cpp index 762bacdb8b..9ca2d09287 100644 --- a/tests/unit/capi/GEOSisSimpleTest.cpp +++ b/tests/unit/capi/GEOSisSimpleTest.cpp @@ -36,11 +36,15 @@ template<> template<> void object::test<3>() { + set_test_name("curved inputs"); + useContext(); + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); ensure(input_ != nullptr); - char ret = GEOSisSimple(input_); - ensure_equals("error raised on curved geometry", ret, 2); + ensure_equals(GEOSisSimple_r(ctxt_, input_), 2); + useCurveConversion(); + ensure_equals(GEOSisSimple_r(ctxt_, input_), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSisValidDetailTest.cpp b/tests/unit/capi/GEOSisValidDetailTest.cpp index a4f8e3a38e..935b37f8da 100644 --- a/tests/unit/capi/GEOSisValidDetailTest.cpp +++ b/tests/unit/capi/GEOSisValidDetailTest.cpp @@ -37,7 +37,9 @@ struct test_capiisvaliddetail_data : public capitest::utility { ~test_capiisvaliddetail_data() { GEOSGeom_destroy(loc_); - GEOSFree(reason_); + if (reason_) { + GEOSFree(reason_); + } } }; @@ -144,11 +146,13 @@ template<> template<> void object::test<7>() { + useContext(); input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); ensure(input_ != nullptr); - char ret = GEOSisValidDetail(input_, 0, nullptr, nullptr); - ensure_equals("error raised on curved geometry", ret, 2); + ensure_equals(GEOSisValidDetail_r(ctxt_, input_, 0, nullptr, nullptr), 2); + useCurveConversion(); + ensure_equals(GEOSisValidDetail_r(ctxt_, input_, 0, nullptr, nullptr), 1); } } // namespace tut diff --git a/tests/unit/capi/GEOSisValidReasonTest.cpp b/tests/unit/capi/GEOSisValidReasonTest.cpp index e3146c4430..3ad4259522 100644 --- a/tests/unit/capi/GEOSisValidReasonTest.cpp +++ b/tests/unit/capi/GEOSisValidReasonTest.cpp @@ -61,11 +61,22 @@ template<> template<> void object::test<7>() { - input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + set_test_name("GEOSIsValidReason with automatic linearization"); + + useContext(); + + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 1 1, 0 0)))"); ensure(input_ != nullptr); - char* reason = GEOSisValidReason(input_); + char* reason = GEOSisValidReason_r(ctxt_, input_); ensure(reason == nullptr); + + useCurveConversion(); + + reason = GEOSisValidReason_r(ctxt_, input_); + ensure(reason != nullptr); + ensure_equals(std::string(reason).substr(0, 17), "Self-intersection"); + GEOSFree_r(ctxt_, reason); } diff --git a/tests/unit/capi/GEOSisValidTest.cpp b/tests/unit/capi/GEOSisValidTest.cpp index 046a53feb0..4f59ca80c7 100644 --- a/tests/unit/capi/GEOSisValidTest.cpp +++ b/tests/unit/capi/GEOSisValidTest.cpp @@ -68,11 +68,15 @@ template<> template<> void object::test<5>() { + set_test_name("GEOSIsValid with automatic linearization"); + useContext(); + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); ensure(input_ != nullptr); - char ret = GEOSisValid(input_); - ensure_equals("error raised on curved geometry", ret, 2); + ensure_equals(GEOSisValid_r(ctxt_, input_), 2); + useCurveConversion(); + ensure_equals(GEOSisValid_r(ctxt_, input_), 1); } } // namespace tut diff --git a/tests/unit/capi/capi_test_utils.h b/tests/unit/capi/capi_test_utils.h index 7cc711ed71..a9680cf1bd 100644 --- a/tests/unit/capi/capi_test_utils.h +++ b/tests/unit/capi/capi_test_utils.h @@ -24,7 +24,11 @@ namespace capitest { GEOSGeometry* result_ = nullptr; GEOSGeometry* expected_ = nullptr; char* wkt_ = nullptr; - char* str_ = nullptr; + char* str_ = nullptr; + + GEOSContextHandle_t ctxt_ = nullptr; + GEOSCurveToLineParams* curveToLineParams_ = nullptr; + GEOSLineToCurveParams* lineToCurveParams_ = nullptr; utility() { @@ -32,6 +36,9 @@ namespace capitest { wktw_ = GEOSWKTWriter_create(); GEOSWKTWriter_setRoundingPrecision(wktw_, 10); + curveToLineParams_ = GEOSCurveToLineParams_create(); + lineToCurveParams_ = GEOSLineToCurveParams_create(); + #ifdef HAVE_FENV std::feclearexcept(FE_ALL_EXCEPT); #endif @@ -39,18 +46,53 @@ namespace capitest { virtual ~utility() { - if (wktw_) GEOSWKTWriter_destroy(wktw_); - if (geom1_) GEOSGeom_destroy(geom1_); - if (geom2_) GEOSGeom_destroy(geom2_); - if (geom3_) GEOSGeom_destroy(geom3_); - if (input_) GEOSGeom_destroy(input_); - if (result_) GEOSGeom_destroy(result_); - if (expected_) GEOSGeom_destroy(expected_); - if (wkt_) GEOSFree(wkt_); - if (str_) GEOSFree(str_); + if (ctxt_ == nullptr) { + if (wktw_) GEOSWKTWriter_destroy(wktw_); + if (geom1_) GEOSGeom_destroy(geom1_); + if (geom2_) GEOSGeom_destroy(geom2_); + if (geom3_) GEOSGeom_destroy(geom3_); + if (input_) GEOSGeom_destroy(input_); + if (result_) GEOSGeom_destroy(result_); + if (expected_) GEOSGeom_destroy(expected_); + if (wkt_) GEOSFree(wkt_); + if (str_) GEOSFree(str_); + if (lineToCurveParams_) GEOSLineToCurveParams_destroy(lineToCurveParams_); + if (curveToLineParams_) GEOSCurveToLineParams_destroy(curveToLineParams_); + finishGEOS(); + } else { + if (wktw_) GEOSWKTWriter_destroy_r(ctxt_, wktw_); + if (geom1_) GEOSGeom_destroy_r(ctxt_, geom1_); + if (geom2_) GEOSGeom_destroy_r(ctxt_, geom2_); + if (geom3_) GEOSGeom_destroy_r(ctxt_, geom3_); + if (input_) GEOSGeom_destroy_r(ctxt_, input_); + if (result_) GEOSGeom_destroy_r(ctxt_, result_); + if (expected_) GEOSGeom_destroy_r(ctxt_, expected_); + if (wkt_) GEOSFree_r(ctxt_, wkt_); + if (str_) GEOSFree_r(ctxt_, str_); + if (lineToCurveParams_) GEOSLineToCurveParams_destroy(lineToCurveParams_); + if (curveToLineParams_) GEOSCurveToLineParams_destroy(curveToLineParams_); + finishGEOS_r(ctxt_); + } + } + + // Use an explicit context handle (stored as ctxt_) instead of the default + // context handle. + void useContext() { finishGEOS(); + ctxt_ = initGEOS_r(notice, notice); + GEOSWKTWriter_setRoundingPrecision(wktw_, 10); } + void useCurveConversion() { + GEOSCurveToLineParams* curveToLineParams = GEOSCurveToLineParams_create(); + GEOSLineToCurveParams* lineToCurveParams = GEOSLineToCurveParams_create(); + + GEOSContext_setCurveToLineParams_r(ctxt_, curveToLineParams); + GEOSContext_setLineToCurveParams_r(ctxt_, lineToCurveParams); + + GEOSCurveToLineParams_destroy(curveToLineParams); + GEOSLineToCurveParams_destroy(lineToCurveParams); + } static void notice(GEOS_PRINTF_FORMAT const char* fmt, ...) GEOS_PRINTF_FORMAT_ATTR(1, 2) { @@ -74,7 +116,7 @@ namespace capitest { GEOSGeometry* fromWKT(const char* wkt) { - GEOSGeometry* g = GEOSGeomFromWKT(wkt); + GEOSGeometry* g = ctxt_ ? GEOSGeomFromWKT_r(ctxt_, wkt) : GEOSGeomFromWKT(wkt); if (g == nullptr) { std::string message = "WKT is invalid: " + std::string(wkt); tut::ensure(message, g != nullptr); @@ -85,9 +127,19 @@ namespace capitest { std::string toWKT(const GEOSGeometry* g) { - char* wkt = GEOSWKTWriter_write(wktw_, g); - std::string ret(wkt); - GEOSFree(wkt); + char* wkt; + std::string ret; + + if (ctxt_ == nullptr) { + wkt = GEOSWKTWriter_write(wktw_, g); + ret = wkt; + GEOSFree(wkt); + } else { + wkt = GEOSWKTWriter_write_r(ctxt_, wktw_, g); + ret = wkt; + GEOSFree_r(ctxt_, wkt); + } + return ret; } @@ -97,6 +149,10 @@ namespace capitest { char rslt; if (g1 == nullptr || g2 == nullptr) { rslt = (g1 == nullptr && g2 == nullptr) ? 1 : 0; + } else if (ctxt_ != nullptr) { + GEOSNormalize_r(ctxt_, g1); + GEOSNormalize_r(ctxt_, g2); + rslt = GEOSEqualsExact_r(ctxt_, g1, g2, tolerance); } else { GEOSNormalize(g1); @@ -114,6 +170,9 @@ namespace capitest { if (g1 == nullptr || g2 == nullptr) { rslt = (g1 == nullptr && g2 == nullptr) ? 1 : 0; } + else if (ctxt_) { + rslt = GEOSEqualsExact_r(ctxt_, g1, g2, tolerance); + } else { rslt = GEOSEqualsExact(g1, g2, tolerance); } @@ -127,8 +186,9 @@ namespace capitest { char rslt; if (g1 == nullptr || g2 == nullptr) { rslt = (g1 == nullptr && g2 == nullptr) ? 1 : 0; - } - else { + } else if (ctxt_) { + rslt = GEOSEqualsIdentical_r(ctxt_, g1, g2); + } else { rslt = GEOSEqualsIdentical(g1, g2); } report_not_equal("ensure_equals_identical", g1, g2, 1e-12, rslt); @@ -167,17 +227,9 @@ namespace capitest { if (rslt == 1) return; //TODO: handle rslt exception value - char* wkt1 = nullptr; - char* wkt2 = nullptr; - if (g1 != nullptr) - wkt1 = GEOSWKTWriter_write(wktw_, g1); - if (g2 != nullptr) - wkt2 = GEOSWKTWriter_write(wktw_, g2); - const char* val1 = (g1 == nullptr) ? "null" : wkt1; - const char* val2 = (g2 == nullptr) ? "null" : wkt2; - std::fprintf(stdout, "\n%s : %s != %s (tol = %f)\n", tag, val1, val2, tolerance); - GEOSFree(wkt1); - GEOSFree(wkt2); + const std::string wkt1 = g1 == nullptr ? "null" : toWKT(g1); + const std::string wkt2 = g2 == nullptr ? "null" : toWKT(g2); + std::fprintf(stdout, "\n%s : %s != %s (tol = %f)\n", tag, wkt1.c_str(), wkt2.c_str(), tolerance); } }; diff --git a/tests/unit/geom/CircularStringTest.cpp b/tests/unit/geom/CircularStringTest.cpp index 6468696dac..a63ff4911e 100644 --- a/tests/unit/geom/CircularStringTest.cpp +++ b/tests/unit/geom/CircularStringTest.cpp @@ -1,6 +1,9 @@ #include #include +#include +#include +#include #include #include #include @@ -10,6 +13,8 @@ #include "utility.h" +using geos::algorithm::CurveToLineParams; +using geos::algorithm::LineToCurveParams; using geos::geom::CoordinateSequence; using geos::geom::CircularString; using XY = geos::geom::CoordinateXY; @@ -44,6 +49,33 @@ struct test_circularstring_data { ensure_equals_exact_geometry_xyzm(cs.get(), expected.get(), 0); } + + void checkLinearizeMaxDeviation(const std::string& wkt_in, const std::string& wkt_expected, double maxDeviation, double tol=1e-12) { + auto ctl = CurveToLineParams::maxDeviation(maxDeviation); + checkLinearize(wkt_in, wkt_expected, ctl, tol); + } + + void checkLinearize(const std::string& wkt_in, const std::string& wkt_expected, double stepSizeDegrees, double tol=1e-12) { + auto ctl = CurveToLineParams::stepSizeDegrees(stepSizeDegrees); + checkLinearize(wkt_in, wkt_expected, ctl, tol); + } + + void checkLinearize(const std::string& wkt_in, const std::string& wkt_expected, const geos::algorithm::CurveToLineParams& params, double tol=1e-12) { + cs_ = wktreader_.read(wkt_in); + + auto ls = cs_->getLinearized(params); + + auto expected = wktreader_.read(wkt_expected); + + ensure_equals_exact_geometry_xyzm(ls.get(), expected.get(), tol); + + auto csRev = cs_->reverse(); + auto lsRev = csRev->getLinearized(params); + auto lsRevRev = lsRev->reverse(); + + ensure_equals_exact_geometry_xyzm(lsRevRev.get(), expected.get(), tol); + } + }; typedef test_group group; @@ -286,4 +318,214 @@ void object::test<8>() "CIRCULARSTRING (-2 2, -3 5, 0 10, -5 5, 0 0)"); } +template<> +template<> +void object::test<9>() +{ + set_test_name("half-circle, evenly divisible by requested step size"); + + // expected result from GDAL 3.12 + checkLinearize("CIRCULARSTRING (0 0, 1 1, 2 0)", + "LINESTRING (0 0, 0.002435949740176 0.069756473744125, 0.009731931258429 0.139173100960061, 0.021852399266194 0.20791169081776, 0.038738304061681 0.275637355817011, 0.060307379214091 0.342020143325669, 0.086454542357401 0.406736643075803, 0.117052407141074 0.469471562785898, 0.151951903843575 0.529919264233229, 0.190983005625057 0.587785252292491, 0.233955556881021 0.642787609686564, 0.280660199661355 0.694658370459024, 0.330869393641151 0.743144825477401, 0.384338524674348 0.788010753606727, 0.440807096529255 0.829037572555052, 0.5 0.866025403784448, 0.561628853210948 0.898794046299173, 0.625393406584095 0.927183854566806, 0.690983005625071 0.951056516295154, 0.75807810440034 0.970295726276021, 0.826351822333095 0.984807753012234, 0.895471536732373 0.99452189536828, 0.965100503297521 0.999390827019113, 1.03489949670251 0.999390827019113, 1.10452846326768 0.99452189536828, 1.17364817766696 0.984807753012234, 1.24192189559972 0.970295726276021, 1.30901699437499 0.951056516295182, 1.37460659341593 0.927183854566806, 1.43837114678911 0.898794046299173, 1.50000000000006 0.866025403784448, 1.55919290347077 0.829037572555052, 1.61566147532568 0.788010753606727, 1.66913060635886 0.743144825477401, 1.71933980033867 0.694658370459024, 1.76604444311903 0.642787609686564, 1.80901699437499 0.587785252292491, 1.84804809615645 0.529919264233229, 1.88294759285895 0.469471562785898, 1.91354545764261 0.406736643075803, 1.93969262078593 0.342020143325669, 1.96126169593833 0.275637355817011, 1.97814760073385 0.20791169081776, 1.99026806874161 0.139173100960068, 1.99756405025983 0.069756473744128, 2 0)", + 4); +} + +template<> +template<> +void object::test<10>() +{ + set_test_name("half-circle, not divisible by requested step size"); + + // expected result from PostGIS 3.6 + // SELECT ST_AsText(ST_CurveToLine('CIRCULARSTRING (0 0, 1 1, 2 0)', radians(23), 2, 1)) + checkLinearize("CIRCULARSTRING (0 0, 1 1, 2 0)", + "LINESTRING(0 0,0.076120467488713 0.38268343236509,0.292893218813453 0.707106781186548,0.61731656763491 0.923879532511287,1 1,1.38268343236509 0.923879532511287,1.707106781186548 0.707106781186548,1.923879532511287 0.38268343236509,2 0)", + 23); +} + +template<> +template<> +void object::test<11>() +{ + set_test_name("liblwgeom: 2 segments per quadrant"); + // SELECT ST_AsText(ST_CurveToLine('CIRCULARSTRING (0 0, 100 100, 200 0)', radians(45), 2, 1), 4); + checkLinearize("CIRCULARSTRING(0 0,100 100,200 0)", + "LINESTRING(0 0,29.2893 70.7107,100 100,170.7107 70.7107,200 0)", 90.0 / 2, 1e-4); +} + +template<> +template<> +void object::test<12>() +{ + set_test_name("liblwgeom: 3 segments per quadrant"); + // SELECT ST_AsText(ST_CurveToLine('CIRCULARSTRING (0 0, 100 100, 200 0)', radians(90.0 / 3), 2, 1), 4); + checkLinearize("CIRCULARSTRING(0 0,100 100,200 0)", + "LINESTRING(0 0,13.3975 50,50 86.6025,100 100,150 86.6025,186.6025 50,200 0)", 90.0 / 3, 1e-4); +} + +template<> +template<> +void object::test<13>() +{ + set_test_name("liblwgeom: 2 segments per quadrant"); + // SELECT ST_AsText(ST_CurveToLine('CIRCULARSTRING(29.2893218813453 70.7106781186548,100 100,200 0)', radians(90.0 / 2), 2, 1), 4); + checkLinearize("CIRCULARSTRING(29.2893218813453 70.7106781186548,100 100,200 0)", + "LINESTRING(29.2893 70.7107,100 100,170.7107 70.7107,200 0)", 90.0 / 2, 1e-4); +} + +template<> +template<> +void object::test<14>() +{ + set_test_name("liblwgeom: 3 segments per quadrant - symmetric"); + // SELECT ST_AsText(ST_CurveToLine('CIRCULARSTRING(29.2893218813453 70.7106781186548,100 100,200 0)', radians(90.0 / 3), 2, 1), 4); + checkLinearize("CIRCULARSTRING(29.2893218813453 70.7106781186548,100 100,200 0)", + "LINESTRING(29.2893 70.7107,69.0983 95.1057,115.6434 98.7688,158.7785 80.9017,189.1007 45.399,200 0)", 90.0 / 3, 1e-4); +} + +template<> +template<> +void object::test<15>() { + set_test_name("liblwgeom: 10 segments per quadrant - circular"); + auto cs = wktreader_.read("CIRCULARSTRING (0 0, 1 0, 0 0)"); + auto ls = cs->getLinearized(CurveToLineParams::stepSizeDegrees(90.0 / 10)); + ensure_equals(ls->getNumPoints(), 41u); // PostGIS test has 40, but this is incorrect + const auto* seq = ls->getCoordinatesRO(); + seq->forEachSegment([](const auto& p0, const auto& p1) { + ensure_equals("segment has expected length", p0.distance(p1), geos::MATH_PI/40, 0.01); + }); +} + +template<> +template<> +void object::test<16>() { + set_test_name("liblwgeom: maximum 10 units difference, symmetric"); + checkLinearizeMaxDeviation("CIRCULARSTRING(0 0,100 100,200 0)", + "LINESTRING(0 0,30 70,100 100,170 70,200 0)", 10, 1.4); +} + +template<> +template<> +void object::test<17>() { + set_test_name("liblwgeom: maximum 20 units difference, symmetric"); + checkLinearizeMaxDeviation("CIRCULARSTRING(0 0,100 100,200 0)", + "LINESTRING(0 0,50 86,150 86,200 0)", 20, 1.4); +} + +template<> +template<> +void object::test<18>() { + set_test_name("liblwgeom: ticket #3772 (1)"); + checkLinearizeMaxDeviation("CIRCULARSTRING(71.96 -65.64,22.2 -18.52,20 50)", + "LINESTRING(72 -66,34 -38,16 4,20 50)", 4, 1.4); +} + +template<> +template<> +void object::test<19>() { + set_test_name("liblwgeom: ticket #3772 (2)"); + checkLinearizeMaxDeviation("CIRCULARSTRING(20 50,22.2 -18.52,71.96 -65.64)", + "LINESTRING(20 50,16 4,34 -38,72 -66)", 4, 1.4); +} + +template<> +template<> +void object::test<20>() { + set_test_name("liblwgeom: ticket #4031, max deviation > 2*radius"); + checkLinearizeMaxDeviation("CIRCULARSTRING(20 50,22.2 -18.52,71.96 -65.64)", + "LINESTRING(20 50,22 -18,72 -66)", 500, 1.4); +} + +template<> +template<> +void object::test<21>() { + //set_test_name("liblwgeom: ticket #4058, big radius, small tolerance"); + //checkLinearizeMaxDeviation("CIRCULARSTRING(2696000.553 1125699.831999999936670, 2695950.552000000141561 1125749.833000000100583, 2695865.195999999996275 1125835.189000)", + //"LINESTRING(2696000 1125700,2695932 1125768,2695866 1125836)", 0.0001); +} + +template<> +template<> +void object::test<22>() { + set_test_name("liblwgeom: direction neutrality"); + auto cs = wktreader_.read("CIRCULARSTRING(71.96 -65.64,22.2 -18.52,20 50)"); + auto csRev = cs->reverse(); + + auto ls1 = cs->getLinearized(CurveToLineParams::stepSizeDegrees(90.0 / 4)); + auto ls2 = cs->reverse()->getLinearized(CurveToLineParams::stepSizeDegrees(90.0 / 4))->reverse(); + + ensure("Linearization of reversed CIRCULARSTRING is not direction neutral", ls1->equalsExact(ls2.get(), 0.0)); +} + +template<> +template<> +void object::test<23>() { + set_test_name("getLinearized() called on linear arc"); + + checkLinearize("CIRCULARSTRING(0 0, 2 1, 4 2)", + "LINESTRING (0 0, 2 1, 4 2)", 90.0 / 4); +} + +template<> +template<> +void object::test<24>() { + set_test_name("getLinearized() called on multi-section CircularString"); + + checkLinearize("CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0)", + "LINESTRING (0 0, 0.0761 0.3827, 0.2929 0.7071, 0.6173 0.9239, 1 1, 1.3827 0.9239, 1.7071 0.7071, 1.9239 0.3827, 2 0, 2.0761 -0.3827, 2.2929 -0.7071, 2.6173 -0.9239, 3 -1, 3.3827 -0.9239, 3.7071 -0.7071, 3.9239 -0.3827, 4 0)", + 90.0 / 4, 1e-4); +} + +template<> +template<> +void object::test<25>() +{ + set_test_name("getLinearized() on various CircularString base classes"); + + auto cs = wktreader_.read("CIRCULARSTRING(0 0, 1 1, 2 0)"); + + const auto params = CurveToLineParams::stepSizeDegrees(45); + + // check that we return LineString* rather than Curve* or Geometry* + std::unique_ptr linearized = cs->getLinearized(params); + + ensure_equals("CircularString::getLinearized", linearized.get()->getGeometryTypeId(), geos::geom::GEOS_LINESTRING); + ensure_equals("SimpleCurve::getLinearized", static_cast(cs.get())->getLinearized(params)->getGeometryTypeId(), geos::geom::GEOS_LINESTRING); + ensure_equals("Curve::getLinearized", static_cast(cs.get())->getLinearized(params)->getGeometryTypeId(), geos::geom::GEOS_LINESTRING); + ensure_equals("Geometry::getLinearized", static_cast(cs.get())->getLinearized(params)->getGeometryTypeId(), geos::geom::GEOS_LINESTRING); +} + +template<> +template<> +void object::test<26>() +{ + set_test_name("getCurved()"); + + auto cs = wktreader_.read("CIRCULARSTRING(0 0, 1 1, 2 0)"); + + // Check that we return Curve* rather than Geometry* + std::unique_ptr curved = cs->getCurved(LineToCurveParams()); + + ensure_equals_exact_geometry_xyzm(curved.get(), cs.get(), 0); +} + +template<> +template<> +void object::test<27>() +{ + set_test_name("getLinearized() Z/M"); + using XYZM = geos::geom::CoordinateXYZM; + + auto cs = wktreader_.read("CIRCULARSTRING ZM (0 5 4 5, 5 0 6 8, 0 -5 30 40)"); + auto ls = cs->getLinearized(CurveToLineParams::stepSizeDegrees(30)); + + const CoordinateSequence* seq = ls->getCoordinatesRO(); + + // line point 1 is 30 degrees from the start of the arc. Z/M interpolated from CircularString points 0 and 1 + ensure_equals_xyzm(seq->getAt(1), XYZM{2.5, 4.33, 4 + (6.0 - 4.0) / 3, 5 + (8.0 - 5.0) / 3}, 1e-3); + + // line point 5 is 30 degrees from end of the arc. Z/M interpolated from CircularString points 1 and 2 + ensure_equals_xyzm(seq->getAt(5), XYZM{2.5, -4.33, 6 + (30.0 - 6)*(2.0/3), 8 + (40.0 - 8)*(2.0/3)}, 1e-3); +} + } diff --git a/tests/unit/geom/CompoundCurveTest.cpp b/tests/unit/geom/CompoundCurveTest.cpp index c7def3e639..f790ee2d97 100644 --- a/tests/unit/geom/CompoundCurveTest.cpp +++ b/tests/unit/geom/CompoundCurveTest.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -10,9 +11,15 @@ #include #include #include +#include #include +#include "geos/algorithm/LineToCurveParams.h" + + +using geos::algorithm::CurveToLineParams; using geos::geom::CompoundCurve; +using geos::geom::Curve; using geos::geom::CoordinateXY; using geos::geom::CoordinateSequence; using geos::geom::SimpleCurve; @@ -349,7 +356,7 @@ template<> template<> void object::test<9>() { - set_test_name("cannot create non-contiguous CompoundCurve"); + set_test_name("construction failure on non-contiguous curves"); std::vector> curves; @@ -408,4 +415,40 @@ void object::test<11>() "COMPOUNDCURVE (CIRCULARSTRING (-10 0, -5 5, 0 0), (0 0, 10 0), CIRCULARSTRING (10 0, 0 -10, -10 0))"); } +template<> +template<> +void object::test<12>() +{ + set_test_name("getLinearized()"); + + auto cc = wktreader_.read("COMPOUNDCURVE (CIRCULARSTRING(-5 0, 0 5, 4 3, 5 0, 0 -5), (0 -5, -5 0))"); + auto ls = cc->getLinearized(CurveToLineParams::stepSizeDegrees(90.0 / 4)); + + ensure_equals(ls->getGeometryTypeId(), geos::geom::GEOS_LINESTRING); + ensure_equals(static_cast(cc.get())->getLinearized(CurveToLineParams::stepSizeDegrees(90.0 / 4))->getGeometryTypeId(), geos::geom::GEOS_LINESTRING); + ensure_equals(static_cast(ls.get())->getLinearized(CurveToLineParams::stepSizeDegrees(90.0 / 4))->getGeometryTypeId(), geos::geom::GEOS_LINESTRING); + + geos::operation::valid::RepeatedPointTester rpt; + ensure(!rpt.hasRepeatedPoint(ls.get())); + + auto expected = wktreader_.read("LINESTRING (-5 0, -4.685 1.7467, -3.7796 3.2733, -2.3979 4.3875, -0.7141 4.9487, 1.0597 4.8864, 2.6999 4.2084, 4 3, 4.8129 1.3551, 4.9776 -0.4723, 4.4721 -2.2361, 3.3644 -3.6987, 1.8036 -4.6634, 0 -5, -5 0)"); + ensure_equals_exact_geometry(static_cast(ls.get()), expected.get(), 1e-4); + + auto ccRev = cc->reverse(); + auto lsRev = ccRev->getLinearized(CurveToLineParams::stepSizeDegrees(90.0 / 4)); + ensure_equals_exact_geometry(static_cast(lsRev->reverse().get()), expected.get(), 1e-4); +} + +template<> +template<> +void object::test<13>() +{ + set_test_name("getCurved()"); + + // check that we return Curve* rather than Geometry* + std::unique_ptr curved = cc_->getCurved(geos::algorithm::LineToCurveParams()); + + ensure_equals_exact_geometry_xyzm(curved.get(), cc_.get(), 0); +} + } diff --git a/tests/unit/geom/CurvePolygonTest.cpp b/tests/unit/geom/CurvePolygonTest.cpp index 0e60aded80..a9a80c9c86 100644 --- a/tests/unit/geom/CurvePolygonTest.cpp +++ b/tests/unit/geom/CurvePolygonTest.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -278,4 +280,31 @@ void object::test<6>() ensure_equals_exact_geometry_xyzm(result.get(), expected.get(), 0); } +template<> +template<> +void object::test<7>() +{ + set_test_name("getLinearized"); + + auto cp = wktreader_.read("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 2 0, 2 1, 2 3, 4 3), (4 3, 4 5, 1 4, 0 0)), CIRCULARSTRING (1.7 1, 1.4 0.4, 1.6 0.4, 1.6 0.5, 1.7 1))"); + + // check that we return Polygon* rather than Geometry* + std::unique_ptr poly = cp->getLinearized(geos::algorithm::CurveToLineParams::stepSizeDegrees(90.0 / 4)); + + auto expected = wktreader_.read("POLYGON ((0 0, 0.2675 -0.3446, 0.6464 -0.5607, 1.0793 -0.6152, 1.5 -0.5, 1.8446 -0.2325, 2.0607 0.1464, 2.1152 0.5793, 2 1, 1.6934 1.4588, 1.5858 2, 1.6934 2.5412, 2 3, 2.4588 3.3066, 3 3.4142, 3.5412 3.3066, 4 3, 4 5, 1 4, 0 0), (1.7 1, 1.5871 1.0537, 1.4623 1.0629, 1.3427 1.0265, 1.2444 0.9492, 1.1806 0.8416, 1.16 0.7183, 1.1855 0.5958, 1.2534 0.4908, 1.3548 0.4175, 1.4757 0.3858, 1.6 0.4, 1.6203 0.705, 1.7 1))"); + + ensure_equals_exact_geometry_xyzm(poly.get(), expected.get(), 1e-4); +} + +template<> +template<> +void object::test<8>() +{ + set_test_name("getCurved"); + + std::unique_ptr curved = cp_->getCurved(geos::algorithm::LineToCurveParams()); + + ensure_equals_exact_geometry_xyzm(curved.get(), cp_.get(), 0); +} + } diff --git a/tests/unit/geom/GeometryCollectionTest.cpp b/tests/unit/geom/GeometryCollectionTest.cpp index 41477bb2ff..95df45ab43 100644 --- a/tests/unit/geom/GeometryCollectionTest.cpp +++ b/tests/unit/geom/GeometryCollectionTest.cpp @@ -6,6 +6,11 @@ #include +#include +#include + +using geos::algorithm::CurveToLineParams; +using geos::algorithm::LineToCurveParams; namespace tut { // @@ -181,4 +186,35 @@ void object::test<9> ensure(gc->hasDimension(geos::geom::Dimension::A)); } +template<> +template<> +void object::test<10>() { + set_test_name("getCurved()"); + + auto gc = readWKT("GEOMETRYCOLLECTION(POINT (3 7), CIRCULARSTRING (0 0, 1 1, 2 0), LINESTRING (1 1, 2 3))"); + + auto linearized = gc->getLinearized(CurveToLineParams::stepSizeDegrees(45)); + + auto expected = readWKT("GEOMETRYCOLLECTION(POINT (3 7), LINESTRING (0 0, 0.292893 0.707107, 1 1, 1.707107 0.707107, 2 0), LINESTRING (1 1, 2 3))"); + + ensure_equals_exact_geometry(linearized.get(), expected.get(), 1e-4); +} + +template<> +template<> +void object::test<11>() { + set_test_name("getCurved()"); + + auto gc = readWKT("GEOMETRYCOLLECTION(POINT (3 7), LINESTRING (0 0, 0.292893 0.707107, 1 1, 1.707107 0.707107, 2 0), LINESTRING (1 1, 2 3))"); + + auto params = LineToCurveParams(); + params.setRadiusTolerance(1e-3); + + auto curved = gc->getCurved(params); + + auto expected = readWKT("GEOMETRYCOLLECTION(POINT (3 7), CIRCULARSTRING (0 0, 1 1, 2 0), LINESTRING (1 1, 2 3))"); + + ensure_equals_exact_geometry(curved.get(), expected.get(), 1e-4); +} + } // namespace tut diff --git a/tests/unit/geom/LineStringTest.cpp b/tests/unit/geom/LineStringTest.cpp index 33d74af9df..bae07c13a3 100644 --- a/tests/unit/geom/LineStringTest.cpp +++ b/tests/unit/geom/LineStringTest.cpp @@ -5,6 +5,8 @@ #include #include // geos +#include +#include #include #include #include @@ -643,6 +645,41 @@ void object::test<35> ensure_equals(out, "POINT ZM (20 21 22 23)"); } +template<> +template<> +void object::test<36>() +{ + set_test_name("getLinearized"); + + // check that we return LineString* rather than Curve* or Geometry* + std::unique_ptr linearized = line_->getLinearized(geos::algorithm::CurveToLineParams::stepSizeDegrees(45)); + + ensure_equals_exact_geometry_xyzm(linearized.get(), line_.get(), 0); +} + +template<> +template<> +void object::test<37>() +{ + set_test_name("getCurved"); + + WKTReader reader; + + auto input = reader.read("LINESTRING (2 2, 2.292893 2.707107, 3 3, 3.707107 2.707107, 4 2, 2 2)"); + + // check that we return Curve* instead of Geometry* + auto params = geos::algorithm::LineToCurveParams(); + params.setRadiusTolerance(1e-3); + + std::unique_ptr curved = input->getCurved(params); + + ensure_equals(curved->getGeometryTypeId(), geos::geom::GEOS_COMPOUNDCURVE); + + auto expected = reader.read("COMPOUNDCURVE (CIRCULARSTRING (2 2, 3 3, 4 2), (4 2, 2 2))"); + + ensure_equals_exact_geometry_xyzm(curved.get(), expected.get(), 1e-6); +} + } // namespace tut diff --git a/tests/unit/geom/MultiCurveTest.cpp b/tests/unit/geom/MultiCurveTest.cpp index 365e77e7b4..771997ca68 100644 --- a/tests/unit/geom/MultiCurveTest.cpp +++ b/tests/unit/geom/MultiCurveTest.cpp @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include #include @@ -202,4 +204,27 @@ void object::test<4>() ensure(!wktreader_.read("MULTICURVE ((0 0, 1 0, 1 1, 0 0), CIRCULARSTRING (3 3, 4 4, 5 3))")->isClosed()); } +template<> +template<> +void object::test<5>() +{ + set_test_name("getLinearized()"); + + // check that we return MultiLineString*, not Geometry* + std::unique_ptr mls = mc_->getLinearized(geos::algorithm::CurveToLineParams::stepSizeDegrees(2)); + + ensure_equals(mls->getGeometryTypeId(), geos::geom::GEOS_MULTILINESTRING); + ensure_equals("getLength()", mls->getLength(), mc_->getLength(), 1e-3); +} + +template<> +template<> +void object::test<6>() +{ + set_test_name("getCurved()"); + + std::unique_ptr curved = mc_->getCurved(geos::algorithm::LineToCurveParams()); + ensure_equals_exact_geometry_xyzm(mc_.get(), curved.get(), 0); +} + } diff --git a/tests/unit/geom/MultiLineStringTest.cpp b/tests/unit/geom/MultiLineStringTest.cpp index 7e7433a9db..1851a26833 100644 --- a/tests/unit/geom/MultiLineStringTest.cpp +++ b/tests/unit/geom/MultiLineStringTest.cpp @@ -3,10 +3,16 @@ #include // geos +#include +#include #include #include #include +#include "utility.h" + +using geos::geom::MultiLineString; + namespace tut { // // Test Group @@ -14,14 +20,14 @@ namespace tut { // Common data used by tests struct test_multilinestring_data { - std::unique_ptr empty_mls_; - std::unique_ptr mls_; + std::unique_ptr empty_mls_; + std::unique_ptr mls_; geos::io::WKTReader reader_; test_multilinestring_data() { - empty_mls_ = reader_.read("MULTILINESTRING EMPTY"); - mls_ = reader_.read("MULTILINESTRING ((0 0, 1 1), (3 3, 4 4))"); + empty_mls_ = reader_.read("MULTILINESTRING EMPTY"); + mls_ = reader_.read("MULTILINESTRING ((0 0, 1 1), (3 3, 4 4))"); } }; @@ -75,6 +81,43 @@ void object::test<4> ensure(!mls_->hasDimension(geos::geom::Dimension::A)); } +template<> +template<> +void object::test<5>() +{ + set_test_name("getLinearized()"); + + // check that return MultiLineString*, not Geometry* + std::unique_ptr linearized = mls_->getLinearized(geos::algorithm::CurveToLineParams::stepSizeDegrees(1)); + + ensure_equals_exact_geometry_xyzm(linearized.get(), mls_.get(), 0); +} + +template<> +template<> +void object::test<6>() +{ + set_test_name("getCurved"); + + auto input = reader_.read("MULTILINESTRING ((3 3, 4 4), (-2 0, -1.414 1.414, 0 2, 1.414 1.414, 2 0, 2 3))"); + + auto params = geos::algorithm::LineToCurveParams(); + + // tolerance is too fine to generate a MultiCurve + params.setRadiusTolerance(1e-9); + // check that we return GeometryCollection*, not Geometry* + std::unique_ptr curved = input->getCurved(params); + ensure_equals(curved->getGeometryTypeId(), geos::geom::GEOS_MULTILINESTRING); + + params.setRadiusTolerance(2e-3); + curved = input->getCurved(params); + ensure_equals(curved->getGeometryTypeId(), geos::geom::GEOS_MULTICURVE); + + auto expected = reader_.read("MULTICURVE ((3 3, 4 4), COMPOUNDCURVE(CIRCULARSTRING (-2 0, 0 2, 2 0), (2 0, 2 3)))"); + + ensure_equals_exact_geometry_xyzm(curved.get(), expected.get(), 1e-3); +} + } // namespace tut diff --git a/tests/unit/geom/MultiPolygonTest.cpp b/tests/unit/geom/MultiPolygonTest.cpp index efe46919cf..465828688f 100644 --- a/tests/unit/geom/MultiPolygonTest.cpp +++ b/tests/unit/geom/MultiPolygonTest.cpp @@ -3,10 +3,14 @@ #include // geos +#include +#include #include #include #include +#include "utility.h" + namespace tut { // // Test Group @@ -73,4 +77,40 @@ void object::test<4> ensure(mp_->hasDimension(geos::geom::Dimension::A)); } +template<> +template<> +void object::test<5>() +{ + set_test_name("correct type returned by getLinearized"); + + ensure(mp_->getLinearized(geos::algorithm::CurveToLineParams::stepSizeDegrees(45))->getGeometryTypeId() == geos::geom::GeometryTypeId::GEOS_MULTIPOLYGON); +} + +template<> +template<> +void object::test<6>() +{ + set_test_name("getCurved"); + + auto input = reader_.read( + "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 2.292893 2.707107, 3 3, 3.707107 2.707107, 4 2, 2 2))," + "((20 0, 30 0, 30 10, 20 0)))"); + + auto params = geos::algorithm::LineToCurveParams(); + + // Tolerance to small to allow conversion to MultiSurface + params.setRadiusTolerance(1e-12); + ensure_equals(input->getCurved(params)->getGeometryTypeId(), geos::geom::GeometryTypeId::GEOS_MULTIPOLYGON); + + params.setRadiusTolerance(1e-3); + auto curved = input->getCurved(params); + + ensure_equals(curved->getGeometryTypeId(), geos::geom::GeometryTypeId::GEOS_MULTISURFACE); + + auto expected = reader_.read("MULTISURFACE (CURVEPOLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), COMPOUNDCURVE (CIRCULARSTRING(2 2, 3 3, 4 2), (4 2, 2 2))), ((20 0, 30 0, 30 10, 20 0)))"); + + ensure_equals_exact_geometry_xyzm(curved.get(), expected.get(), 1e-3); +} + + } // namespace tut diff --git a/tests/unit/geom/MultiSurfaceTest.cpp b/tests/unit/geom/MultiSurfaceTest.cpp index 1b1d3509a4..3027a4f99f 100644 --- a/tests/unit/geom/MultiSurfaceTest.cpp +++ b/tests/unit/geom/MultiSurfaceTest.cpp @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include #include @@ -174,4 +176,29 @@ void object::test<3>() auto cc3 = ms_->reverse(); } +template<> +template<> +void object::test<4>() +{ + set_test_name("getLinearized()"); + + // check that we return MultiPolygon* + std::unique_ptr mp_ = ms_->getLinearized(geos::algorithm::CurveToLineParams::stepSizeDegrees(4)); + + ensure_equals(mp_->getGeometryTypeId(), geos::geom::GEOS_MULTIPOLYGON); +} + +template<> +template<> +void object::test<5>() +{ + set_test_name("getCurved()"); + + std::unique_ptr curved = ms_->getCurved(geos::algorithm::LineToCurveParams()); + + ensure_equals_exact_geometry_xyzm(ms_.get(), curved.get(), 0); +} + + + } diff --git a/tests/unit/geom/PointTest.cpp b/tests/unit/geom/PointTest.cpp index fc13bf9bc2..dbfa5c8afe 100644 --- a/tests/unit/geom/PointTest.cpp +++ b/tests/unit/geom/PointTest.cpp @@ -3,6 +3,8 @@ #include // geos +#include +#include #include #include #include @@ -18,6 +20,8 @@ #include #include +#include "utility.h" + constexpr int MAX_TESTS = 100; namespace tut { @@ -630,5 +634,23 @@ void object::test<48> ensure(!point_->hasDimension(geos::geom::Dimension::A)); } +template<> +template<> +void object::test<49>() +{ + set_test_name("getLinearized"); + + ensure_equals_exact_geometry_xyzm(point_.get(), point_->getLinearized(geos::algorithm::CurveToLineParams::stepSizeDegrees(4)).get(), 0); +} + +template<> +template<> +void object::test<50>() +{ + set_test_name("getCurved"); + + ensure_equals_exact_geometry_xyzm(point_.get(), point_->getCurved(geos::algorithm::LineToCurveParams()).get(), 0); +} + } // namespace tut diff --git a/tests/unit/geom/PolygonTest.cpp b/tests/unit/geom/PolygonTest.cpp index 24cafef0c0..1abe1bbd4c 100644 --- a/tests/unit/geom/PolygonTest.cpp +++ b/tests/unit/geom/PolygonTest.cpp @@ -4,6 +4,8 @@ #include #include // geos +#include +#include #include #include #include @@ -748,4 +750,40 @@ void object::test<47> ensure(empty_poly_zm_->hasM()); } +template<> +template<> +void object::test<49>() +{ + set_test_name("getLinearized"); + + // check that we return Polygon* rather than Geometry* + std::unique_ptr linearized = poly_zm_->getLinearized(geos::algorithm::CurveToLineParams::stepSizeDegrees(4)); + + ensure_equals_exact_geometry_xyzm(linearized.get(), poly_zm_.get(), 0); +} + +template<> +template<> +void object::test<50>() +{ + set_test_name("getCurved"); + + WKTReader reader; + auto input = reader.read("POLYGON ((0 0, 0.2675 -0.3446, 0.6464 -0.5607, 1.0793 -0.6152, 1.5 -0.5, 1.8446 -0.2325, 2.0607 0.1464, 2.1152 0.5793, 2 1, 1.6934 1.4588, 1.5858 2, 1.6934 2.5412, 2 3, 2.4588 3.3066, 3 3.4142, 3.5412 3.3066, 4 3, 4 5, 1 4, 0 0), (1.7 1, 1.5871 1.0537, 1.4623 1.0629, 1.3427 1.0265, 1.2444 0.9492, 1.1806 0.8416, 1.16 0.7183, 1.1855 0.5958, 1.2534 0.4908, 1.3548 0.4175, 1.4757 0.3858, 1.6 0.4, 1.6203 0.705, 1.7 1))"); + + // check that we return Surface* rather than Geometry* + auto params = geos::algorithm::LineToCurveParams(); + params.setRadiusTolerance(0.01); + + std::unique_ptr curved = input->getCurved(params); + + ensure_equals(curved->getGeometryTypeId(), geos::geom::GeometryTypeId::GEOS_CURVEPOLYGON); + + std::cout << curved->toText() << std::endl; + + auto expected = reader.read("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1.501026632988956 -0.500132555776532, 2 1, 1.999860522668543 2.999400752659415, 4 3), (4 3, 4 5, 1 4, 0 0)), COMPOUNDCURVE (CIRCULARSTRING (1.7 1, 1.16517094963354 0.7813913411683361, 1.6 0.4), (1.6 0.4, 1.6203 0.705, 1.7 1)))"); + + ensure_equals_exact_geometry_xyzm(curved.get(), expected.get(), 1e-2); +} + } // namespace tut diff --git a/tests/unit/utility.h b/tests/unit/utility.h index 21825988b4..f9973fd8f7 100644 --- a/tests/unit/utility.h +++ b/tests/unit/utility.h @@ -445,6 +445,7 @@ ensure_equals_exact_geometry_xyzm(const geos::geom::Geometry *lhs_in, assert(nullptr != rhs_in); using geos::geom::Point; + using geos::geom::CompoundCurve; using geos::geom::Curve; using geos::geom::CompoundCurve; using geos::geom::SimpleCurve; @@ -546,6 +547,9 @@ ensure_equals_exact_geometry(const geos::geom::Geometry *lhs_in, assert(nullptr != lhs_in); assert(nullptr != rhs_in); + using geos::geom::Point; + using geos::geom::SimpleCurve; + using geos::geom::Surface; using geos::geom::CoordinateSequence; using geos::geom::Curve; using geos::geom::GeometryCollection;