[geos-commits] [SCM] GEOS branch main updated. 52dc9337bc485361adfa0e3e0f0c135ceb4ccbdd
git at osgeo.org
git at osgeo.org
Tue Apr 7 07:39:24 PDT 2026
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GEOS".
The branch, main has been updated
via 52dc9337bc485361adfa0e3e0f0c135ceb4ccbdd (commit)
via c0169aacd10c62a9a2d4c0b4e0e469327a2b78bf (commit)
via 25ea72cd522bad00110276d2643ace910fbb3866 (commit)
via 99696486a8c454550367941384225a3c9f95ce14 (commit)
via dfa7c7ad43e5db1cd4ef5903ea3bf26070936faa (commit)
via d1a9893c4c3a9867a2149d49a9f21c6331703bd2 (commit)
from 3f8560ef422af5f245941ea7fd6702ac4f9261e8 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 52dc9337bc485361adfa0e3e0f0c135ceb4ccbdd
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Apr 7 09:47:17 2026 -0400
GEOSPolygonize_full: add test with curved inputs
diff --git a/tests/unit/capi/GEOSPolygonizeTest.cpp b/tests/unit/capi/GEOSPolygonizeTest.cpp
index 99e666f2c..5de4a27c7 100644
--- a/tests/unit/capi/GEOSPolygonizeTest.cpp
+++ b/tests/unit/capi/GEOSPolygonizeTest.cpp
@@ -277,6 +277,45 @@ void object::test<9>()
ensure_equals(GEOSGetSRID(result_), 4326);
}
+template<>
+template<>
+void object::test<10>()
+{
+ set_test_name("GEOSPolygonize_full with curved inputs");
+
+ input_ = fromWKT("MULTICURVE (CIRCULARSTRING (0 0, 1 1, 2 0), CIRCULARSTRING (2 0, 3 1, 4 0), LINESTRING (2 0, 0 0))");
+ GEOSSetSRID(input_, 4326);
+
+ GEOSGeometry* cuts;
+ GEOSGeometry* dangles;
+ GEOSGeometry* invalidRings;
+
+ result_ = GEOSPolygonize_full(input_, &cuts, &dangles, &invalidRings);
+ ensure(result_);
+
+ expected_ = fromWKT("GEOMETRYCOLLECTION (CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0))))");
+ GEOSGeometry* expected_cuts = GEOSGeomFromWKT("GEOMETRYCOLLECTION EMPTY");
+ GEOSGeometry* expected_dangles = GEOSGeomFromWKT("GEOMETRYCOLLECTION(CIRCULARSTRING (2 0, 3 1, 4 0))");
+ GEOSGeometry* expected_invalidRings = GEOSGeomFromWKT("GEOMETRYCOLLECTION EMPTY");
+
+ ensure_geometry_equals(result_, expected_);
+ ensure_geometry_equals(cuts, expected_cuts);
+ ensure_geometry_equals(dangles, expected_dangles);
+ ensure_geometry_equals(invalidRings, expected_invalidRings);
+
+ ensure_equals(GEOSGetSRID(result_), 4326);
+ ensure_equals(GEOSGetSRID(cuts), 4326);
+ ensure_equals(GEOSGetSRID(dangles), 4326);
+ ensure_equals(GEOSGetSRID(invalidRings), 4326);
+
+ GEOSGeom_destroy(cuts);
+ GEOSGeom_destroy(dangles);
+ GEOSGeom_destroy(invalidRings);
+
+ GEOSGeom_destroy(expected_cuts);
+ GEOSGeom_destroy(expected_dangles);
+ GEOSGeom_destroy(expected_invalidRings);
+}
} // namespace tut
commit c0169aacd10c62a9a2d4c0b4e0e469327a2b78bf
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Apr 7 09:35:20 2026 -0400
GEOSPolygonize: Propagate input SRID
diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index b79283c02..fe58078a5 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -2444,13 +2444,19 @@ extern "C" {
// Polygonize
Polygonizer plgnzr;
+ int srid = 0;
for(std::size_t i = 0; i < ngeoms; ++i) {
plgnzr.add(g[i]);
+ if (!srid) {
+ srid = g[i]->getSRID();
+ }
}
auto polys = plgnzr.getSurfaces();
const GeometryFactory* gf = handle->geomFactory;
- return gf->createGeometryCollection(std::move(polys)).release();
+ auto out = gf->createGeometryCollection(std::move(polys));
+ out->setSRID(srid);
+ return out.release();
});
}
@@ -2461,39 +2467,41 @@ extern "C" {
return execute(extHandle, [&]() -> Geometry* {
GEOSContextHandleInternal_t* handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
- Geometry* out;
+ std::unique_ptr<Geometry> out;
// Polygonize
Polygonizer plgnzr(true);
int srid = 0;
for(std::size_t i = 0; i < ngeoms; ++i) {
plgnzr.add(g[i]);
- srid = g[i]->getSRID();
+ if (!srid) {
+ srid = g[i]->getSRID();
+ }
}
auto surfaces = plgnzr.getSurfaces();
if (surfaces.empty()) {
- out = handle->geomFactory->createGeometryCollection().release();
+ out = handle->geomFactory->createGeometryCollection();
} else if (surfaces.size() == 1) {
- return surfaces[0].release();
+ out = std::move(surfaces[0]);
} else {
const bool isCurved = std::any_of(surfaces.begin(), surfaces.end(), [](const auto& poly) {
return poly->hasCurvedComponents();
});
if (isCurved) {
- return handle->geomFactory->createMultiSurface(std::move(surfaces)).release();
+ out = handle->geomFactory->createMultiSurface(std::move(surfaces));
} else {
std::vector<std::unique_ptr<Polygon>> polygons(surfaces.size());
for (std::size_t i = 0; i < surfaces.size(); i++) {
polygons[i].reset(geos::detail::down_cast<Polygon*>(surfaces[i].release()));
}
- return handle->geomFactory->createMultiPolygon(std::move(polygons)).release();
+ out = handle->geomFactory->createMultiPolygon(std::move(polygons));
}
}
out->setSRID(srid);
- return out;
+ return out.release();
});
}
@@ -2622,7 +2630,9 @@ extern "C" {
int srid = 0;
for(std::size_t i = 0; i < ngeoms; ++i) {
plgnzr.add(g[i]);
- srid = g[i]->getSRID();
+ if (srid == 0) {
+ srid = g[i]->getSRID();
+ }
}
const std::vector<const SimpleCurve*>& lines = plgnzr.getCutEdges();
@@ -2655,6 +2665,7 @@ extern "C" {
return execute(extHandle, [&]() {
// Polygonize
Polygonizer plgnzr;
+ const int srid = g->getSRID();
for(std::size_t i = 0; i < g->getNumGeometries(); ++i) {
plgnzr.add(g->getGeometryN(i));
}
@@ -2669,6 +2680,7 @@ extern "C" {
}
*cuts = gf->createGeometryCollection(std::move(linevec)).release();
+ (*cuts)->setSRID(srid);
}
if(dangles) {
@@ -2679,6 +2691,7 @@ extern "C" {
}
*dangles = gf->createGeometryCollection(std::move(linevec)).release();
+ (*dangles)->setSRID(srid);
}
if(invalid) {
@@ -2689,12 +2702,13 @@ extern "C" {
}
*invalid = gf->createGeometryCollection(std::move(linevec)).release();
+ (*invalid)->setSRID(srid);
}
auto polys = plgnzr.getSurfaces();
- Geometry* out = gf->createGeometryCollection(std::move(polys)).release();
- out->setSRID(g->getSRID());
- return out;
+ auto out = gf->createGeometryCollection(std::move(polys));
+ out->setSRID(srid);
+ return out.release();
});
}
diff --git a/tests/unit/capi/GEOSPolygonizeTest.cpp b/tests/unit/capi/GEOSPolygonizeTest.cpp
index eb3d07e7d..99e666f2c 100644
--- a/tests/unit/capi/GEOSPolygonizeTest.cpp
+++ b/tests/unit/capi/GEOSPolygonizeTest.cpp
@@ -32,18 +32,20 @@ template<>
void object::test<1>
()
{
+ set_test_name("GEOSPolygonizer_getCutEdges");
+
constexpr int size = 2;
GEOSGeometry* geoms[size] = { nullptr };
geoms[0] = GEOSGeomFromWKT("LINESTRING(1 3, 3 3, 3 1, 1 1, 1 3)");
geoms[1] = GEOSGeomFromWKT("LINESTRING(1 3, 3 3, 3 1, 1 1, 1 3)");
+ GEOSSetSRID(geoms[0], 4326);
- GEOSGeometry* g = GEOSPolygonizer_getCutEdges(geoms, size);
+ result_ = GEOSPolygonizer_getCutEdges(geoms, size);
- ensure(nullptr != g);
- ensure_equals(GEOSGetNumGeometries(g), size);
-
- GEOSGeom_destroy(g);
+ ensure(result_);
+ ensure_equals(GEOSGetNumGeometries(result_), size);
+ ensure_equals(GEOSGetSRID(result_), 4326);
for(auto& input : geoms) {
GEOSGeom_destroy(input);
@@ -83,16 +85,20 @@ template<>
void object::test<3>
()
{
+ set_test_name("two nested rings");
+
constexpr int size = 2;
GEOSGeometry* geoms[size];
geoms[0] = GEOSGeomFromWKT("LINESTRING (100 100, 100 300, 300 300, 300 100, 100 100)");
geoms[1] = GEOSGeomFromWKT("LINESTRING (150 150, 150 250, 250 250, 250 150, 150 150)");
+ GEOSSetSRID(geoms[0], 4326);
// GEOSPolygonize gives us a collection of two polygons
GEOSGeometry* g = GEOSPolygonize(geoms, size);
ensure(nullptr != g);
ensure_equals(GEOSGetNumGeometries(g), 2);
ensure_equals(GEOSGeomTypeId(g), GEOS_GEOMETRYCOLLECTION);
+ ensure_equals(GEOSGetSRID(g), 4326);
GEOSGeom_destroy(g);
// GEOSPolygonize_valid gives us a single polygon with a hole
@@ -101,6 +107,7 @@ void object::test<3>
ensure(nullptr != g);
ensure_equals(GEOSGetNumGeometries(g), 1);
ensure_equals(GEOSGeomTypeId(g), GEOS_POLYGON);
+ ensure_equals(GEOSGetSRID(g), 4326);
GEOSGeom_destroy(g);
for(auto& input : geoms) {
@@ -113,16 +120,19 @@ template<>
void object::test<4>
()
{
+ set_test_name("GEOSPolygonize_valid producing an empty GeometryCollection");
+
constexpr int size = 1;
GEOSGeometry* geoms[size];
geoms[0] = GEOSGeomFromWKT("LINESTRING (0 0, 1 1)");
+ GEOSSetSRID(geoms[0], 4326);
- GEOSGeometry* g = GEOSPolygonize_valid(geoms, size);
+ result_ = GEOSPolygonize_valid(geoms, size);
- ensure(nullptr != g);
- ensure_equals(GEOSGetNumGeometries(g), 0);
- ensure_equals(GEOSGeomTypeId(g), GEOS_GEOMETRYCOLLECTION);
- GEOSGeom_destroy(g);
+ ensure(result_);
+ ensure_equals(GEOSGetNumGeometries(result_), 0);
+ ensure_equals(GEOSGeomTypeId(result_), GEOS_GEOMETRYCOLLECTION);
+ ensure_equals(GEOSGetSRID(result_), 4326);
for(auto& input : geoms) {
GEOSGeom_destroy(input);
@@ -134,17 +144,20 @@ template<>
void object::test<5>
()
{
+ set_test_name("GEOSPolygonize_valid producing a MultiPolygon");
+
constexpr int size = 2;
GEOSGeometry* geoms[size];
geoms[0] = GEOSGeomFromWKT("LINESTRING (0 0, 1 0, 1 1, 0 1, 0 0)");
geoms[1] = GEOSGeomFromWKT("LINESTRING (1 1, 2 1, 2 2, 1 2, 1 1)");
+ GEOSSetSRID(geoms[0], 4326);
- GEOSGeometry* g = GEOSPolygonize_valid(geoms, size);
+ result_ = GEOSPolygonize_valid(geoms, size);
- ensure(nullptr != g);
- ensure_equals(GEOSGetNumGeometries(g), 2);
- ensure_equals(GEOSGeomTypeId(g), GEOS_MULTIPOLYGON);
- GEOSGeom_destroy(g);
+ ensure(result_);
+ ensure_equals(GEOSGetNumGeometries(result_), 2);
+ ensure_equals(GEOSGeomTypeId(result_), GEOS_MULTIPOLYGON);
+ ensure_equals(GEOSGetSRID(result_), 4326);
for(auto& input : geoms) {
GEOSGeom_destroy(input);
@@ -157,7 +170,10 @@ template<>
void object::test<6>
()
{
+ set_test_name("GEOSPolygonize_full with MultiLineString input");
+
geom1_ = GEOSGeomFromWKT("MULTILINESTRING ((0 0, 1 0, 1 1, 0 1, 0 0), (0 0, 0.5 0.5), (1 1, 2 2, 1 2, 2 1, 1 1))");
+ GEOSSetSRID(geom1_, 4326);
GEOSGeometry* cuts;
GEOSGeometry* dangles;
@@ -175,6 +191,11 @@ void object::test<6>
ensure_geometry_equals(dangles, expected_dangles);
ensure_geometry_equals(invalidRings, expected_invalidRings);
+ ensure_equals(GEOSGetSRID(result_), 4326);
+ ensure_equals(GEOSGetSRID(cuts), 4326);
+ ensure_equals(GEOSGetSRID(dangles), 4326);
+ ensure_equals(GEOSGetSRID(invalidRings), 4326);
+
GEOSGeom_destroy(cuts);
GEOSGeom_destroy(dangles);
GEOSGeom_destroy(invalidRings);
@@ -195,6 +216,7 @@ void object::test<7>
GEOSGeometry* geoms[size];
geoms[0] = GEOSGeomFromWKT("LINESTRING (0 0, 2 0)");
geoms[1] = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)");
+ GEOSSetSRID(geoms[0], 4326);
for (auto& geom : geoms) {
ensure(geom != nullptr);
@@ -205,6 +227,7 @@ void object::test<7>
expected_ = fromWKT("GEOMETRYCOLLECTION( CURVEPOLYGON (COMPOUNDCURVE((0 0, 2 0), CIRCULARSTRING (2 0, 1 1, 0 0))))");
ensure_geometry_equals(result_, expected_);
+ ensure_equals(GEOSGetSRID(result_), 4326);
for(auto& input : geoms) {
GEOSGeom_destroy(input);
@@ -216,7 +239,7 @@ template<>
void object::test<8>
()
{
- set_test_name("LINESTRING ZM inputs");
+ set_test_name("GEOSPolygonize with LINESTRING ZM inputs");
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)");
@@ -241,6 +264,7 @@ void object::test<9>()
set_test_name("GEOSPolygonize_valid with curved inputs");
input_ = fromWKT("MULTICURVE ((10 0, 0 0, 0 10, 10 10), CIRCULARSTRING (10 10, 5 5, 10 0), COMPOUNDCURVE ((10 10, 20 10, 20 0, 10 0), CIRCULARSTRING (10 0, 15 5, 10 10)))");
+ GEOSSetSRID(input_, 4326);
result_ = GEOSPolygonize_valid(&input_, 1);
ensure(result_);
@@ -250,6 +274,7 @@ void object::test<9>()
"CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (10 0, 15 5, 10 10), (10 10, 20 10, 20 0, 10 0))))");
ensure_geometry_equals(result_, expected_);
+ ensure_equals(GEOSGetSRID(result_), 4326);
}
commit 25ea72cd522bad00110276d2643ace910fbb3866
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Mar 31 13:37:07 2026 -0400
Polygonizer: support curved inputs
diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index 82b6127af..c941d50dd 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -5267,6 +5267,7 @@ extern GEOSGeometry GEOS_DLL *GEOSNode(const GEOSGeometry* g);
* linework is extracted as the edges to be polygonized.
*
* Z and M values from the input linework will be preserved.
+* Curved geometries are supported since GEOS 3.15.
*
* The edges must be correctly noded; that is, they must only meet
* at their endpoints and not overlap anywhere. If your edges are not
@@ -5295,7 +5296,7 @@ extern GEOSGeometry GEOS_DLL *GEOSNode(const GEOSGeometry* g);
* that the input lines form a valid polygonal geometry (which may
* include holes or nested polygons).
*
-* \param geoms Array of linear geometries to polygons. Caller retains ownership of both array container and objects.
+* \param geoms Array of linear geometries to polygonize. Caller retains ownership of both array container and objects.
* \param ngeoms Size of the geoms array.
* \return The polygonal output geometry.
* Caller is responsible for freeing with GEOSGeom_destroy().
@@ -5312,7 +5313,10 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonize(
* but returns a result which is a valid polygonal geometry.
* The result will not contain any edge-adjacent elements.
*
-* \param geoms Array of linear geometries to polygons. Caller retains ownership of both array container and objects.
+* Z and M values from the input linework will be preserved.
+* Curved geometries are supported since GEOS 3.15.
+*
+* \param geoms Array of linear geometries to polygonize. Caller retains ownership of both array container and objects.
* \param ngeoms Size of the geoms array.
* \return The polygonal output geometry.
* Caller is responsible for freeing with GEOSGeom_destroy().
@@ -5330,7 +5334,10 @@ 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.
+* Z and M values from the input linework will be preserved.
+* Curved geometries are supported since GEOS 3.15.
+*
+* \param geoms Array of linear geometries to polygonize. 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().
@@ -5346,6 +5353,9 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonizer_getCutEdges(
* Perform the polygonization as GEOSPolygonize() and return the
* polygonal result as well as all extra outputs.
*
+* Z and M values from the input linework will be preserved.
+* Curved geometries are supported since GEOS 3.15.
+*
* \param[in] input A single geometry with all the input lines to polygonize.
* \param[out] cuts Pointer to hold "cut edges", connected on both ends but not part of output. Caller must free.
* \param[out] dangles Pointer to hold "dangles", connected one end but not part of output. Caller must free.
diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index d183be9ea..b79283c02 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -2448,7 +2448,7 @@ extern "C" {
plgnzr.add(g[i]);
}
- auto polys = plgnzr.getPolygons();
+ auto polys = plgnzr.getSurfaces();
const GeometryFactory* gf = handle->geomFactory;
return gf->createGeometryCollection(std::move(polys)).release();
});
@@ -2471,13 +2471,25 @@ extern "C" {
srid = g[i]->getSRID();
}
- auto polys = plgnzr.getPolygons();
- if (polys.empty()) {
+ auto surfaces = plgnzr.getSurfaces();
+ if (surfaces.empty()) {
out = handle->geomFactory->createGeometryCollection().release();
- } else if (polys.size() == 1) {
- return polys[0].release();
+ } else if (surfaces.size() == 1) {
+ return surfaces[0].release();
} else {
- return handle->geomFactory->createMultiPolygon(std::move(polys)).release();
+ const bool isCurved = std::any_of(surfaces.begin(), surfaces.end(), [](const auto& poly) {
+ return poly->hasCurvedComponents();
+ });
+
+ if (isCurved) {
+ return handle->geomFactory->createMultiSurface(std::move(surfaces)).release();
+ } else {
+ std::vector<std::unique_ptr<Polygon>> polygons(surfaces.size());
+ for (std::size_t i = 0; i < surfaces.size(); i++) {
+ polygons[i].reset(geos::detail::down_cast<Polygon*>(surfaces[i].release()));
+ }
+ return handle->geomFactory->createMultiPolygon(std::move(polygons)).release();
+ }
}
out->setSRID(srid);
@@ -2613,7 +2625,7 @@ extern "C" {
srid = g[i]->getSRID();
}
- const std::vector<const LineString*>& lines = plgnzr.getCutEdges();
+ const std::vector<const SimpleCurve*>& lines = plgnzr.getCutEdges();
// We need a vector of Geometry pointers, not Polygon pointers.
// STL vector doesn't allow transparent upcast of this
@@ -2650,7 +2662,7 @@ extern "C" {
const GeometryFactory* gf = g->getFactory();
if(cuts) {
- const std::vector<const LineString*>& lines = plgnzr.getCutEdges();
+ const std::vector<const SimpleCurve*>& lines = plgnzr.getCutEdges();
std::vector<std::unique_ptr<Geometry>> linevec(lines.size());
for(std::size_t i = 0, n = lines.size(); i < n; ++i) {
linevec[i] = lines[i]->clone();
@@ -2660,7 +2672,7 @@ extern "C" {
}
if(dangles) {
- const std::vector<const LineString*>& lines = plgnzr.getDangles();
+ const std::vector<const SimpleCurve*>& lines = plgnzr.getDangles();
std::vector<std::unique_ptr<Geometry>> linevec(lines.size());
for(std::size_t i = 0, n = lines.size(); i < n; ++i) {
linevec[i] = lines[i]->clone();
@@ -2670,7 +2682,7 @@ extern "C" {
}
if(invalid) {
- const std::vector<std::unique_ptr<LineString>>& lines = plgnzr.getInvalidRingLines();
+ const std::vector<std::unique_ptr<Curve>>& lines = plgnzr.getInvalidRingLines();
std::vector<std::unique_ptr<Geometry>> linevec(lines.size());
for(std::size_t i = 0, n = lines.size(); i < n; ++i) {
linevec[i] = lines[i]->clone();
@@ -2679,7 +2691,7 @@ extern "C" {
*invalid = gf->createGeometryCollection(std::move(linevec)).release();
}
- auto polys = plgnzr.getPolygons();
+ auto polys = plgnzr.getSurfaces();
Geometry* out = gf->createGeometryCollection(std::move(polys)).release();
out->setSRID(g->getSRID());
return out;
diff --git a/include/geos/operation/polygonize/EdgeRing.h b/include/geos/operation/polygonize/EdgeRing.h
index cbfb6fdc7..5f8b7df5b 100644
--- a/include/geos/operation/polygonize/EdgeRing.h
+++ b/include/geos/operation/polygonize/EdgeRing.h
@@ -20,7 +20,7 @@
#pragma once
#include <geos/export.h>
-#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
+#include <geos/algorithm/locate/PointOnGeometryLocator.h>
#include <geos/operation/polygonize/PolygonizeDirectedEdge.h>
#include <geos/geom/Geometry.h>
#include <geos/geom/LinearRing.h>
@@ -38,6 +38,7 @@
// Forward declarations
namespace geos {
namespace geom {
+class Curve;
class LineString;
class CoordinateSequence;
class GeometryFactory;
@@ -64,11 +65,10 @@ private:
DeList deList;
// cache the following data for efficiency
- mutable std::unique_ptr<geom::LinearRing> ring;
- mutable std::shared_ptr<geom::CoordinateSequence> ringPts;
+ mutable std::unique_ptr<geom::Curve> ring;
mutable std::unique_ptr<algorithm::locate::PointOnGeometryLocator> ringLocator;
- std::unique_ptr<std::vector<std::unique_ptr<geom::LinearRing>>> holes;
+ std::vector<std::unique_ptr<geom::Curve>> holes;
EdgeRing* shell = nullptr;
bool is_hole;
@@ -78,14 +78,6 @@ private:
bool is_included = false;
bool visitedByUpdateIncludedRecursive = false;
- /** \brief
- * Computes the list of coordinates which are contained in this ring.
- * The coordinates are computed once only and cached.
- *
- * @return an array of the Coordinate in this ring
- */
- const geom::CoordinateSequence* getCoordinates() const;
-
const geom::Envelope& getEnvelope() const {
return *getRingInternal()->getEnvelopeInternal();
}
@@ -94,12 +86,7 @@ private:
bool isForward,
geom::CoordinateSequence* coordList);
- algorithm::locate::PointOnGeometryLocator* getLocator() const {
- if (ringLocator == nullptr) {
- ringLocator.reset(new algorithm::locate::IndexedPointInAreaLocator(*getRingInternal()));
- }
- return ringLocator.get();
- }
+ algorithm::locate::PointOnGeometryLocator* getLocator() const;
bool contains(const EdgeRing& otherRing) const;
bool isPointInOrOut(const EdgeRing& otherRing) const;
@@ -130,7 +117,7 @@ public:
* @return containing EdgeRing, if there is one
* @return null if no containing EdgeRing is found
*/
- EdgeRing* findEdgeRingContaining(const std::vector<EdgeRing*> & erList);
+ EdgeRing* findEdgeRingContaining(const std::vector<EdgeRing*> & erList) const;
/**
* \brief
@@ -184,7 +171,7 @@ public:
/** \brief
* Tests whether this ring is a hole.
*
- * Due to the way the edges in the polyongization graph are linked,
+ * Due to the way the edges in the polygonization graph are linked,
* a ring is a hole if it is oriented counter-clockwise.
* @return <code>true</code> if this ring is a hole
*/
@@ -292,7 +279,7 @@ public:
*
* @param hole the LinearRing forming the hole.
*/
- void addHole(geom::LinearRing* hole);
+ void addHole(std::unique_ptr<geom::Curve> hole);
void addHole(EdgeRing* holeER);
@@ -304,7 +291,7 @@ public:
*
* @return the Polygon formed by this ring and its holes.
*/
- std::unique_ptr<geom::Polygon> getPolygon();
+ std::unique_ptr<geom::Surface> getPolygon();
/** \brief
* Tests if the LinearRing ring formed by this edge ring
@@ -315,32 +302,20 @@ public:
void computeValid();
/** \brief
- * Gets the coordinates for this ring as a LineString.
- *
- * Used to return the coordinates in this ring
- * as a valid geometry, when it has been detected that the ring
- * is topologically invalid.
- * @return a LineString containing the coordinates in this ring
- */
- std::unique_ptr<geom::LineString> getLineString();
-
- /** \brief
- * Returns this ring as a LinearRing, or null if an Exception
+ * Returns this ring or null if an Exception
* occurs while creating it (such as a topology problem).
*
- * Ownership of ring is retained by the object.
- * Details of problems are written to standard output.
+ * Ownership of the ring is retained by the object.
*/
- const geom::LinearRing* getRingInternal() const;
+ const geom::Curve* getRingInternal() const;
/** \brief
- * Returns this ring as a LinearRing, or null if an Exception
+ * Returns this ring or null if an Exception
* occurs while creating it (such as a topology problem).
*
- * Details of problems are written to standard output.
- * Caller gets ownership of ring.
+ * Caller gets ownership of the ring.
*/
- std::unique_ptr<geom::LinearRing> getRingOwnership();
+ std::unique_ptr<geom::Curve> getRingOwnership();
geom::Location locate(const geom::CoordinateXY& pt) const {
return getLocator()->locate(&pt);
diff --git a/include/geos/operation/polygonize/PolygonizeEdge.h b/include/geos/operation/polygonize/PolygonizeEdge.h
index d58d9b8f5..7592cf2e5 100644
--- a/include/geos/operation/polygonize/PolygonizeEdge.h
+++ b/include/geos/operation/polygonize/PolygonizeEdge.h
@@ -26,7 +26,7 @@
// Forward declarations
namespace geos {
namespace geom {
-class LineString;
+class SimpleCurve;
}
}
@@ -42,14 +42,14 @@ namespace polygonize { // geos::operation::polygonize
class GEOS_DLL PolygonizeEdge: public planargraph::Edge {
private:
// Externally owned
- const geom::LineString* line;
+ const geom::SimpleCurve* line;
public:
// Keep the given pointer (won't do anything to it)
- PolygonizeEdge(const geom::LineString* newLine);
+ PolygonizeEdge(const geom::SimpleCurve* newLine);
// Just return what it was given initially
- const geom::LineString* getLine() const;
+ const geom::SimpleCurve* getLine() const;
};
} // namespace geos::operation::polygonize
diff --git a/include/geos/operation/polygonize/PolygonizeGraph.h b/include/geos/operation/polygonize/PolygonizeGraph.h
index e85dbba1b..52746d242 100644
--- a/include/geos/operation/polygonize/PolygonizeGraph.h
+++ b/include/geos/operation/polygonize/PolygonizeGraph.h
@@ -33,10 +33,12 @@
// Forward declarations
namespace geos {
namespace geom {
-class LineString;
-class GeometryFactory;
+class CircularString;
class Coordinate;
class CoordinateSequence;
+class GeometryFactory;
+class LineString;
+class SimpleCurve;
}
namespace planargraph {
class Node;
@@ -94,6 +96,13 @@ public:
*/
void addEdge(const geom::LineString* line);
+ /**
+ * \brief
+ * Add a CircularString forming an edge of the polygon graph.
+ * @param geom the CircularString to add
+ */
+ void addEdge(const geom::CircularString* geom);
+
/**
* \brief
* Computes the EdgeRings formed by the edges in this graph.
@@ -108,12 +117,12 @@ public:
* \brief
* Finds and removes all cut edges from the graph.
*
- * @param cutLines : the list of the LineString forming the removed
+ * @param cutLines : the list of the geometries forming the removed
* cut edges will be pushed here.
*
* TODO: document ownership of the returned LineStrings
*/
- void deleteCutEdges(std::vector<const geom::LineString*>& cutLines);
+ void deleteCutEdges(std::vector<const geom::SimpleCurve*>& cutLines);
/** \brief
* Marks all edges from the graph which are "dangles".
@@ -124,10 +133,10 @@ public:
* In order to handle large recursion depths efficiently,
* an explicit recursion stack is used
*
- * @param dangleLines : the LineStrings that formed dangles will
+ * @param dangleLines : the geometries that formed dangles will
* be push_back'ed here
*/
- void deleteDangles(std::vector<const geom::LineString*>& dangleLines);
+ void deleteDangles(std::vector<const geom::SimpleCurve*>& dangleLines);
private:
diff --git a/include/geos/operation/polygonize/Polygonizer.h b/include/geos/operation/polygonize/Polygonizer.h
index 1c21a3a34..6d17ecbdb 100644
--- a/include/geos/operation/polygonize/Polygonizer.h
+++ b/include/geos/operation/polygonize/Polygonizer.h
@@ -72,7 +72,7 @@ namespace polygonize { // geos::operation::polygonize
* - <b>Invalid Ring Lines</b> - edges which form rings which are invalid
* (e.g. the component lines contain a self-intersection)
*
- * The Polygonizer constructor allows extracting only polygons which form a
+ * The Polygonizer constructor allows extracting only (Curve)Polygons which form a
* valid polygonal result.
* The set of extracted polygons is guaranteed to be edge-disjoint.
* This is useful when it is known that the input lines form a valid
@@ -84,23 +84,23 @@ private:
/**
* Add every linear element in a geometry into the polygonizer graph.
*/
- class GEOS_DLL LineStringAdder: public geom::GeometryComponentFilter {
+ class GEOS_DLL SimpleCurveAdder: public geom::GeometryComponentFilter {
public:
Polygonizer* pol;
- explicit LineStringAdder(Polygonizer* p);
+ explicit SimpleCurveAdder(Polygonizer* p);
//void filter_rw(geom::Geometry *g);
void filter_ro(const geom::Geometry* g) override;
};
// default factory
- LineStringAdder lineStringAdder;
+ SimpleCurveAdder lineStringAdder;
/**
- * Add a linestring to the graph of polygon edges.
+ * Add a curve to the graph of polygon edges.
*
- * @param line the {@link LineString} to add
+ * @param line the {@link SimpleCurve} to add
*/
- void add(const geom::LineString* line);
+ void add(const geom::SimpleCurve* line);
/**
* Perform the polygonization, if it has not already been carried out.
@@ -116,11 +116,11 @@ private:
* discarding rings which correspond to outer rings and hence contain
* duplicate linework.
*/
- std::vector<std::unique_ptr<geom::LineString>> extractInvalidLines(
- std::vector<EdgeRing*>& invalidRings);
+ static std::vector<std::unique_ptr<geom::Curve> > extractInvalidLines(
+ std::vector<EdgeRing *> &invalidRings);
/**
- * Tests if a invalid ring should be included in
+ * Tests if an invalid ring should be included in
* the list of reported invalid rings.
*
* Rings are included only if they contain
@@ -134,7 +134,7 @@ private:
* @param invalidRing the ring to test
* @return true if the ring should be included
*/
- bool isIncludedInvalid(EdgeRing* invalidRing);
+ static bool isIncludedInvalid(const EdgeRing* invalidRing);
void findShellsAndHoles(const std::vector<EdgeRing*>& edgeRingList);
@@ -142,7 +142,7 @@ private:
static void findOuterShells(std::vector<EdgeRing*>& shellList);
- static std::vector<std::unique_ptr<geom::Polygon>> extractPolygons(std::vector<EdgeRing*> & shellList, bool includeAll);
+ static std::vector<std::unique_ptr<geom::Surface>> extractPolygons(std::vector<EdgeRing*> & shellList, bool includeAll);
bool extractOnlyPolygonal;
bool computed;
@@ -152,13 +152,13 @@ protected:
std::unique_ptr<PolygonizeGraph> graph;
// initialize with empty collections, in case nothing is computed
- std::vector<const geom::LineString*> dangles;
- std::vector<const geom::LineString*> cutEdges;
- std::vector<std::unique_ptr<geom::LineString>> invalidRingLines;
+ std::vector<const geom::SimpleCurve*> dangles;
+ std::vector<const geom::SimpleCurve*> cutEdges;
+ std::vector<std::unique_ptr<geom::Curve>> invalidRingLines;
std::vector<EdgeRing*> holeList;
std::vector<EdgeRing*> shellList;
- std::vector<std::unique_ptr<geom::Polygon>> polyList;
+ std::vector<std::unique_ptr<geom::Surface>> polyList;
public:
@@ -203,22 +203,34 @@ public:
void add(const geom::Geometry* g);
/** \brief
- * Gets the list of polygons formed by the polygonization.
+ * Gets the list of Polygons formed by the polygonization.
+ * Any CurvePolygons formed will be omitted. This function
+ * is provided for backward-compatibility with callers
+ * expecting a return type of Polygon.
*
- * Ownership of vector is transferred to caller, subsequent
- * calls will return NULL.
+ * Ownership of geometries is transferred to caller. Subsequent
+ * calls will return an empty vector.
* @return a collection of Polygons
*/
std::vector<std::unique_ptr<geom::Polygon>> getPolygons();
+ /** \brief
+ * Gets the list of (Curve)Polygons formed by the polygonization.
+ *
+ * Ownership of geometries is transferred to caller. Subsequent
+ * calls will return an empty vector.
+ * @return a collection of Polygons
+ */
+ std::vector<std::unique_ptr<geom::Surface>> getSurfaces();
+
/** \brief
* Get the list of dangling lines found during polygonization.
*
* @return a (possibly empty) collection of pointers to
- * the input LineStrings which are dangles.
+ * the input LineStrings or CircularStrings which are dangles.
*
*/
- const std::vector<const geom::LineString*>& getDangles();
+ const std::vector<const geom::SimpleCurve*>& getDangles();
bool hasDangles();
@@ -226,10 +238,10 @@ public:
* Get the list of cut edges found during polygonization.
*
* @return a (possibly empty) collection of pointers to
- * the input LineStrings which are cut edges.
+ * the input LineStrings or CircularStrings which are cut edges.
*
*/
- const std::vector<const geom::LineString*>& getCutEdges();
+ const std::vector<const geom::SimpleCurve*>& getCutEdges();
bool hasCutEdges();
@@ -238,17 +250,17 @@ public:
* polygonization.
*
* @return a (possibly empty) collection of pointers to
- * the input LineStrings which form invalid rings
+ * the invalid rings
*
*/
- const std::vector<std::unique_ptr<geom::LineString>>& getInvalidRingLines();
+ const std::vector<std::unique_ptr<geom::Curve>>& getInvalidRingLines();
bool hasInvalidRingLines();
bool allInputsFormPolygons();
-// This seems to be needed by GCC 2.95.4
- friend class Polygonizer::LineStringAdder;
+// This seems to be needed by GCC 2.95.4
+ friend class Polygonizer::SimpleCurveAdder;
};
} // namespace geos::operation::polygonize
diff --git a/src/geom/util/CurveBuilder.cpp b/src/geom/util/CurveBuilder.cpp
index 314001e72..0fd0f491b 100644
--- a/src/geom/util/CurveBuilder.cpp
+++ b/src/geom/util/CurveBuilder.cpp
@@ -47,7 +47,7 @@ CurveBuilder::add(const Curve& geom)
void
CurveBuilder::add(const CoordinateSequence& coords, bool isCurved)
{
- getSeq(isCurved).add(*coords, false);
+ getSeq(isCurved).add(coords, false);
}
void
diff --git a/src/operation/polygonize/EdgeRing.cpp b/src/operation/polygonize/EdgeRing.cpp
index 74457d3e9..091715936 100644
--- a/src/operation/polygonize/EdgeRing.cpp
+++ b/src/operation/polygonize/EdgeRing.cpp
@@ -23,13 +23,16 @@
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/LinearRing.h>
#include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateFilter.h>
#include <geos/geom/Envelope.h>
#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/util/CurveBuilder.h>
#include <geos/algorithm/PointLocation.h>
#include <geos/algorithm/Orientation.h>
+#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
+#include <geos/algorithm/locate/SimplePointInAreaLocator.h>
#include <geos/util/IllegalArgumentException.h>
#include <geos/util.h> // TODO: drop this, includes too much
-#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
#include <geos/geom/Location.h>
#include <vector>
@@ -49,7 +52,7 @@ namespace polygonize { // geos.operation.polygonize
/*public*/
EdgeRing*
-EdgeRing::findEdgeRingContaining(const std::vector<EdgeRing*> & erList)
+EdgeRing::findEdgeRingContaining(const std::vector<EdgeRing*> & erList) const
{
EdgeRing* minContainingRing = nullptr;
for (auto& edgeRing : erList) {
@@ -110,8 +113,6 @@ EdgeRing::EdgeRing(const GeometryFactory* newFactory)
:
factory(newFactory),
ring(nullptr),
- ringPts(nullptr),
- holes(nullptr),
is_hole(false)
{
#ifdef DEBUG_ALLOC
@@ -141,37 +142,70 @@ void
EdgeRing::computeHole()
{
getRingInternal();
- is_hole = Orientation::isCCW(ring->getCoordinatesRO());
+
+ if (ring->getGeometryTypeId() == GEOS_COMPOUNDCURVE) {
+ auto coords = ring->getCoordinates();
+ is_hole = Orientation::isCCW(coords.get());
+ } else {
+ const SimpleCurve* sc = detail::down_cast<SimpleCurve*>(ring.get());
+ is_hole = Orientation::isCCW(sc->getCoordinatesRO());
+ }
}
/*public*/
void
-EdgeRing::addHole(LinearRing* hole)
+EdgeRing::addHole(std::unique_ptr<Curve> hole)
{
- if(holes == nullptr) {
- holes.reset(new std::vector<std::unique_ptr<LinearRing>>());
- }
- holes->emplace_back(hole);
+ holes.push_back(std::move(hole));
}
void
EdgeRing::addHole(EdgeRing* holeER) {
holeER->setShell(this);
auto hole = holeER->getRingOwnership(); // TODO is this right method?
- addHole(hole.release());
+ addHole(std::move(hole));
}
/*public*/
-std::unique_ptr<Polygon>
+std::unique_ptr<Surface>
EdgeRing::getPolygon()
{
- if (holes) {
- return factory->createPolygon(std::move(ring), std::move(*holes));
+ if (!holes.empty()) {
+ return factory->createSurface(std::move(ring), std::move(holes));
} else {
- return factory->createPolygon(std::move(ring));
+ return factory->createSurface(std::move(ring));
}
}
+// Adapter class to check whether a point lies within a ring.
+// Unlike IndexedPointInAreaLocator, SimplePointInAreaLocator does not treat
+// closed rings as areas.
+class PointInCurvedRingLocator : public algorithm::locate::PointOnGeometryLocator {
+ public:
+ explicit PointInCurvedRingLocator(const Curve& ring) : m_ring(ring) {}
+
+ geom::Location locate(const geom::CoordinateXY* p) override {
+ return PointLocation::locateInRing(*p, m_ring);
+ }
+
+ private:
+ const Curve& m_ring;
+};
+
+algorithm::locate::PointOnGeometryLocator*
+EdgeRing::getLocator() const
+{
+ if (ringLocator == nullptr) {
+ const auto* rng = getRingInternal();
+ if (rng->hasCurvedComponents()) {
+ ringLocator = std::make_unique<PointInCurvedRingLocator>(*rng);
+ } else {
+ ringLocator = std::make_unique<algorithm::locate::IndexedPointInAreaLocator>(*rng);
+ }
+ }
+ return ringLocator.get();
+}
+
/*public*/
bool
EdgeRing::isValid() const
@@ -182,13 +216,19 @@ EdgeRing::isValid() const
void
EdgeRing::computeValid()
{
- getCoordinates();
- if (ringPts->size() <= 3) {
+ const Curve* r = getRingInternal();
+
+ if (r->getNumPoints() <= 3) {
is_valid = false;
return;
}
- getRingInternal();
- is_valid = ring->isValid();
+
+ if (r->getGeometryTypeId() == GEOS_LINEARRING) {
+ is_valid = getRingInternal()->isValid();
+ } else {
+ // TODO: Change once IsValidOp supports curves
+ is_valid = true;
+ }
}
bool
@@ -206,24 +246,51 @@ EdgeRing::contains(const EdgeRing& otherRing) const {
bool
EdgeRing::isPointInOrOut(const EdgeRing& otherRing) const {
// in most cases only one or two points will be checked
- for (const CoordinateXY& pt : otherRing.getCoordinates()->items<CoordinateXY>()) {
- geom::Location loc = locate(pt);
- if (loc == geom::Location::INTERIOR) {
- return true;
+
+ struct PointTester : public CoordinateFilter {
+
+ public:
+ explicit PointTester(const EdgeRing* p_ring) : m_er(p_ring), m_result(false) {}
+
+ void filter_ro(const CoordinateXY* pt) override {
+ Location loc = m_er->locate(*pt);
+
+ if (loc == geom::Location::INTERIOR) {
+ m_done = true;
+ m_result = true;
+ } else if (loc == geom::Location::EXTERIOR) {
+ m_done = true;
+ m_result = false;
+ }
+
+ // pt is on BOUNDARY, so keep checking for a determining location
}
- if (loc == geom::Location::EXTERIOR) {
- return false;
+
+ bool isDone() const override {
+ return m_done;
}
- // pt is on BOUNDARY, so keep checking for a determining location
- }
- return false;
+
+ bool getResult() const {
+ return m_result;
+ }
+
+ private:
+ const EdgeRing* m_er;
+ bool m_done{false};
+ bool m_result;
+ };
+
+ PointTester tester(this);
+ otherRing.getRingInternal()->apply_ro(&tester);
+
+ return tester.getResult();
}
/*private*/
-const CoordinateSequence*
-EdgeRing::getCoordinates() const
+const Curve*
+EdgeRing::getRingInternal() const
{
- if(ringPts == nullptr) {
+ if (ring == nullptr) {
bool hasZ = false;
bool hasM = false;
@@ -233,51 +300,26 @@ EdgeRing::getCoordinates() const
hasM |= edge->getLine()->hasM();
}
- ringPts = std::make_shared<CoordinateSequence>(0u, hasZ, hasM);
+ geom::util::CurveBuilder builder(*factory, hasZ, hasM);
for(const auto& de : deList) {
- auto edge = detail::down_cast<PolygonizeEdge*>(de->getEdge());
+ const auto edge = detail::down_cast<PolygonizeEdge*>(de->getEdge());
+ const bool isCurved = edge->getLine()->getGeometryTypeId() == GEOS_CIRCULARSTRING;
+
+ CoordinateSequence& ringPts = builder.getSeq(isCurved);
+
addEdge(edge->getLine()->getCoordinatesRO(),
- de->getEdgeDirection(), ringPts.get());
+ de->getEdgeDirection(), &ringPts);
}
- }
- return ringPts.get();
-}
-/*public*/
-std::unique_ptr<LineString>
-EdgeRing::getLineString()
-{
- getCoordinates();
- return factory->createLineString(ringPts);
-}
-
-/*public*/
-const LinearRing*
-EdgeRing::getRingInternal() const
-{
- if(ring != nullptr) {
- return ring.get();
+ ring = builder.getGeometry();
}
- getCoordinates();
- try {
- ring = factory->createLinearRing(*ringPts);
- }
- catch(const geos::util::IllegalArgumentException& e) {
-#if GEOS_DEBUG
- // FIXME: print also ringPts
- std::cerr << "EdgeRing::getRingInternal: "
- << e.what()
- << std::endl;
-#endif
- ::geos::ignore_unused_variable_warning(e);
- }
return ring.get();
}
/*public*/
-std::unique_ptr<LinearRing>
+std::unique_ptr<Curve>
EdgeRing::getRingOwnership()
{
getRingInternal(); // active lazy generation
diff --git a/src/operation/polygonize/PolygonizeEdge.cpp b/src/operation/polygonize/PolygonizeEdge.cpp
index d2d210cc5..a1837c066 100644
--- a/src/operation/polygonize/PolygonizeEdge.cpp
+++ b/src/operation/polygonize/PolygonizeEdge.cpp
@@ -26,12 +26,12 @@ namespace geos {
namespace operation { // geos.operation
namespace polygonize { // geos.operation.polygonize
-PolygonizeEdge::PolygonizeEdge(const LineString* newLine)
+PolygonizeEdge::PolygonizeEdge(const SimpleCurve* newLine)
{
line = newLine;
}
-const LineString*
+const SimpleCurve*
PolygonizeEdge::getLine() const
{
return line;
diff --git a/src/operation/polygonize/PolygonizeGraph.cpp b/src/operation/polygonize/PolygonizeGraph.cpp
index b19de4749..a8c7d0b69 100644
--- a/src/operation/polygonize/PolygonizeGraph.cpp
+++ b/src/operation/polygonize/PolygonizeGraph.cpp
@@ -25,6 +25,7 @@
#include <geos/planargraph/Node.h>
#include <geos/planargraph/DirectedEdgeStar.h>
#include <geos/planargraph/DirectedEdge.h>
+#include <geos/geom/CircularString.h>
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/LineString.h>
#include <geos/util.h>
@@ -156,6 +157,46 @@ PolygonizeGraph::addEdge(const LineString* line)
newCoords.push_back(linePts.release());
}
+void
+PolygonizeGraph::addEdge(const CircularString* geom)
+{
+ if(geom->isEmpty()) {
+ return;
+ }
+
+ const CoordinateSequence* linePts = geom->getCoordinatesRO();
+
+ const CoordinateXY& startPt = linePts->getAt<CoordinateXY>(0);
+ const CoordinateXY& endPt = linePts->getAt<CoordinateXY>(linePts->getSize() - 1);
+ Node* nStart = getNode(startPt);
+ Node* nEnd = getNode(endPt);
+
+ DirectedEdge* de0;
+ DirectedEdge* de1;
+
+ // Forward edge
+ {
+ CircularArc startArc(*linePts, 0);
+ CoordinateXY dirPt = startArc.getDirectionPoint();
+ de0 = new PolygonizeDirectedEdge(nStart, nEnd, dirPt, true);
+ }
+
+ // Reverse edge
+ {
+ CircularArc endArc = CircularArc(*linePts, linePts->getSize() - 3).reverse();
+ CoordinateXY dirPt = endArc.getDirectionPoint();
+ de1 = new PolygonizeDirectedEdge(nEnd, nStart, dirPt, false);
+ }
+
+ newDirEdges.push_back(de0);
+ newDirEdges.push_back(de1);
+
+ Edge* edge = new PolygonizeEdge(geom);
+ newEdges.push_back(edge);
+ edge->setDirectedEdges(de0, de1);
+ add(edge);
+}
+
Node*
PolygonizeGraph::getNode(const CoordinateXY& pt)
{
@@ -276,7 +317,7 @@ PolygonizeGraph::findLabeledEdgeRings(std::vector<DirectedEdge*>& dirEdges,
/* public */
void
-PolygonizeGraph::deleteCutEdges(std::vector<const LineString*>& cutLines)
+PolygonizeGraph::deleteCutEdges(std::vector<const SimpleCurve*>& cutLines)
{
computeNextCWEdges();
@@ -427,12 +468,12 @@ PolygonizeGraph::findEdgeRing(PolygonizeDirectedEdge* startDE)
/* public */
void
-PolygonizeGraph::deleteDangles(std::vector<const LineString*>& dangleLines)
+PolygonizeGraph::deleteDangles(std::vector<const SimpleCurve*>& dangleLines)
{
std::vector<Node*> nodeStack;
findNodesOfDegree(1, nodeStack);
- std::set<const LineString*> uniqueDangles;
+ std::set<const SimpleCurve*> uniqueDangles;
while(!nodeStack.empty()) {
Node* node = nodeStack.back();
@@ -448,7 +489,7 @@ PolygonizeGraph::deleteDangles(std::vector<const LineString*>& dangleLines)
}
// save the line as a dangle
auto e = detail::down_cast<PolygonizeEdge*>(de->getEdge());
- const LineString* ls = e->getLine();
+ const SimpleCurve* ls = e->getLine();
if(uniqueDangles.insert(ls).second) {
dangleLines.push_back(ls);
}
diff --git a/src/operation/polygonize/Polygonizer.cpp b/src/operation/polygonize/Polygonizer.cpp
index 92eefdfc0..4b8c7246b 100644
--- a/src/operation/polygonize/Polygonizer.cpp
+++ b/src/operation/polygonize/Polygonizer.cpp
@@ -22,8 +22,11 @@
#include <geos/operation/polygonize/PolygonizeGraph.h>
#include <geos/operation/polygonize/EdgeRing.h>
#include <geos/operation/polygonize/HoleAssigner.h>
+#include <geos/geom/CircularString.h>
+#include <geos/geom/CompoundCurve.h>
#include <geos/geom/LineString.h>
#include <geos/geom/Geometry.h>
+#include <geos/geom/GeometryFactory.h>
#include <geos/geom/Polygon.h>
#include <geos/util/Interrupt.h>
// std
@@ -44,17 +47,25 @@ namespace geos {
namespace operation { // geos.operation
namespace polygonize { // geos.operation.polygonize
-Polygonizer::LineStringAdder::LineStringAdder(Polygonizer* p):
+Polygonizer::SimpleCurveAdder::SimpleCurveAdder(Polygonizer* p):
pol(p)
{
}
void
-Polygonizer::LineStringAdder::filter_ro(const Geometry* g)
+Polygonizer::SimpleCurveAdder::filter_ro(const Geometry* g)
{
- auto ls = dynamic_cast<const LineString*>(g);
- if(ls) {
- pol->add(ls);
+ if (g->getGeometryTypeId() == GEOS_COMPOUNDCURVE) {
+ const auto& cc = static_cast<const CompoundCurve&>(*g);
+ for (std::size_t i = 0; i < cc.getNumCurves(); i++) {
+ filter_ro(cc.getCurveN(i));
+ }
+
+ return;
+ }
+
+ if(const auto sc = dynamic_cast<const SimpleCurve*>(g)) {
+ pol->add(sc);
}
}
@@ -71,14 +82,6 @@ Polygonizer::Polygonizer(bool onlyPolygonal):
{
}
-/*
- * Add a collection of geometries to be polygonized.
- * May be called multiple times.
- * Any dimension of Geometry may be added;
- * the constituent linework will be extracted and used
- *
- * @param geomList a list of {@link Geometry}s with linework to be polygonized
- */
void
Polygonizer::add(std::vector<Geometry*>* geomList)
{
@@ -87,14 +90,6 @@ Polygonizer::add(std::vector<Geometry*>* geomList)
}
}
-/*
- * Add a collection of geometries to be polygonized.
- * May be called multiple times.
- * Any dimension of Geometry may be added;
- * the constituent linework will be extracted and used
- *
- * @param geomList a list of {@link Geometry}s with linework to be polygonized
- */
void
Polygonizer::add(std::vector<const Geometry*>* geomList)
{
@@ -103,49 +98,55 @@ Polygonizer::add(std::vector<const Geometry*>* geomList)
}
}
-/*
- * Add a geometry to the linework to be polygonized.
- * May be called multiple times.
- * Any dimension of Geometry may be added;
- * the constituent linework will be extracted and used
- *
- * @param g a Geometry with linework to be polygonized
- */
void
Polygonizer::add(const Geometry* g)
{
- util::ensureNoCurvedComponents(g);
g->apply_ro(&lineStringAdder);
}
-/*
- * Add a linestring to the graph of polygon edges.
- *
- * @param line the LineString to add
- */
void
-Polygonizer::add(const LineString* line)
+Polygonizer::add(const SimpleCurve* line)
{
// create a new graph using the factory from the input Geometry
if(graph == nullptr) {
graph.reset(new PolygonizeGraph(line->getFactory()));
}
- graph->addEdge(line);
+
+ if (line->getGeometryTypeId() == GEOS_CIRCULARSTRING) {
+ graph->addEdge(detail::down_cast<const CircularString*>(line));
+ } else {
+ graph->addEdge(detail::down_cast<const LineString*>(line));
+ }
}
-/*
- * Gets the list of polygons formed by the polygonization.
- * @return a collection of Polygons
- */
std::vector<std::unique_ptr<Polygon>>
Polygonizer::getPolygons()
{
polygonize();
+
+ std::vector<std::unique_ptr<Polygon>> ret;
+ ret.reserve(polyList.size());
+ for (auto& surf : polyList) {
+ if (surf->getGeometryTypeId() == GEOS_POLYGON) {
+ ret.emplace_back(detail::down_cast<Polygon*>(surf.release()));
+ }
+ }
+ polyList.clear();
+
+ return ret;
+}
+
+std::vector<std::unique_ptr<Surface>>
+Polygonizer::getSurfaces()
+{
+ polygonize();
+
return std::move(polyList);
}
+
/* public */
-const std::vector<const LineString*>&
+const std::vector<const SimpleCurve*>&
Polygonizer::getDangles()
{
polygonize();
@@ -159,7 +160,7 @@ Polygonizer::hasDangles() {
}
/* public */
-const std::vector<const LineString*>&
+const std::vector<const SimpleCurve*>&
Polygonizer::getCutEdges()
{
polygonize();
@@ -174,7 +175,7 @@ Polygonizer::hasCutEdges()
}
/* public */
-const std::vector<std::unique_ptr<LineString>>&
+const std::vector<std::unique_ptr<Curve>>&
Polygonizer::getInvalidRingLines()
{
polygonize();
@@ -265,7 +266,7 @@ Polygonizer::findValidRings(const std::vector<EdgeRing*>& edgeRingList,
}
}
-std::vector<std::unique_ptr<geom::LineString>>
+std::vector<std::unique_ptr<geom::Curve>>
Polygonizer::extractInvalidLines(std::vector<EdgeRing*>& invalidRings)
{
/**
@@ -286,10 +287,15 @@ Polygonizer::extractInvalidLines(std::vector<EdgeRing*>& invalidRings)
* which is either valid or marked as not processed.
* This avoids including outer rings which have linework which is duplicated.
*/
- std::vector<std::unique_ptr<LineString>> invalidLines;
+ std::vector<std::unique_ptr<Curve>> invalidLines;
for (EdgeRing* er : invalidRings) {
if (isIncludedInvalid(er)) {
- invalidLines.push_back(er->getLineString());
+ auto ringGeom = er->getRingOwnership();
+ if (ringGeom->getGeometryTypeId() == GEOS_LINEARRING) {
+ ringGeom = ringGeom->getFactory()->createLineString(ringGeom->getCoordinates());
+ }
+
+ invalidLines.push_back(std::move(ringGeom));
}
er->setProcessed(true);
}
@@ -298,7 +304,7 @@ Polygonizer::extractInvalidLines(std::vector<EdgeRing*>& invalidRings)
}
bool
-Polygonizer::isIncludedInvalid(EdgeRing* invalidRing)
+Polygonizer::isIncludedInvalid(const EdgeRing* invalidRing)
{
for (const PolygonizeDirectedEdge* de: invalidRing->getEdges()) {
const PolygonizeDirectedEdge* deAdj = static_cast<PolygonizeDirectedEdge*>(de->getSym());
@@ -358,10 +364,10 @@ Polygonizer::findOuterShells(std::vector<EdgeRing*> & shells)
}
}
-std::vector<std::unique_ptr<Polygon>>
+std::vector<std::unique_ptr<Surface>>
Polygonizer::extractPolygons(std::vector<EdgeRing*> & shells, bool includeAll)
{
- std::vector<std::unique_ptr<Polygon>> polys;
+ std::vector<std::unique_ptr<Surface>> polys;
for (EdgeRing* er : shells) {
if (includeAll || er->isIncluded()) {
polys.emplace_back(er->getPolygon());
diff --git a/tests/unit/capi/GEOSBuildAreaTest.cpp b/tests/unit/capi/GEOSBuildAreaTest.cpp
index 503003dd6..934aec586 100644
--- a/tests/unit/capi/GEOSBuildAreaTest.cpp
+++ b/tests/unit/capi/GEOSBuildAreaTest.cpp
@@ -38,11 +38,14 @@ template<>
template<>
void object::test<2>()
{
+ set_test_name("curved inputs");
+
input_ = fromWKT("MULTICURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0) )");
- ensure(input_ != nullptr);
result_ = GEOSBuildArea(input_);
- ensure(result_ == nullptr);
+ ensure(result_);
+
+ expected_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))");
}
} // namespace tut
diff --git a/tests/unit/capi/GEOSPolygonizeTest.cpp b/tests/unit/capi/GEOSPolygonizeTest.cpp
index 145743ccc..eb3d07e7d 100644
--- a/tests/unit/capi/GEOSPolygonizeTest.cpp
+++ b/tests/unit/capi/GEOSPolygonizeTest.cpp
@@ -189,6 +189,8 @@ template<>
void object::test<7>
()
{
+ set_test_name("GEOSPolygonize with curved inputs");
+
constexpr int size = 2;
GEOSGeometry* geoms[size];
geoms[0] = GEOSGeomFromWKT("LINESTRING (0 0, 2 0)");
@@ -198,9 +200,11 @@ void object::test<7>
ensure(geom != nullptr);
}
- GEOSGeometry* g = GEOSPolygonize(geoms, size);
+ result_ = GEOSPolygonize(geoms, size);
+ ensure(result_);
- ensure("curved geometries not supported", g == nullptr);
+ expected_ = fromWKT("GEOMETRYCOLLECTION( CURVEPOLYGON (COMPOUNDCURVE((0 0, 2 0), CIRCULARSTRING (2 0, 1 1, 0 0))))");
+ ensure_geometry_equals(result_, expected_);
for(auto& input : geoms) {
GEOSGeom_destroy(input);
@@ -230,5 +234,24 @@ void object::test<8>
ensure_geometry_equals_identical(result_, expected_);
}
+template<>
+template<>
+void object::test<9>()
+{
+ set_test_name("GEOSPolygonize_valid with curved inputs");
+
+ input_ = fromWKT("MULTICURVE ((10 0, 0 0, 0 10, 10 10), CIRCULARSTRING (10 10, 5 5, 10 0), COMPOUNDCURVE ((10 10, 20 10, 20 0, 10 0), CIRCULARSTRING (10 0, 15 5, 10 10)))");
+
+ result_ = GEOSPolygonize_valid(&input_, 1);
+ ensure(result_);
+
+ expected_ = fromWKT("MULTISURFACE ("
+ "CURVEPOLYGON (COMPOUNDCURVE ((0 0, 0 10, 10 10), CIRCULARSTRING (10 10, 5 5, 10 0), (10 0, 0 0))),"
+ "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (10 0, 15 5, 10 10), (10 10, 20 10, 20 0, 10 0))))");
+
+ ensure_geometry_equals(result_, expected_);
+}
+
+
} // namespace tut
diff --git a/tests/unit/geom/util/CurveBuilderTest.cpp b/tests/unit/geom/util/CurveBuilderTest.cpp
index 4276bd9ce..80e140f08 100644
--- a/tests/unit/geom/util/CurveBuilderTest.cpp
+++ b/tests/unit/geom/util/CurveBuilderTest.cpp
@@ -73,7 +73,7 @@ void object::test<2>()
auto expected = reader_.read("COMPOUNDCURVE ((-6 0, -5 0), CIRCULARSTRING (-5 0, 0 5, 5 0), (5 0, 6 0), CIRCULARSTRING (6 0, 0 6, -6 0))");
- ensure_equals_geometry(result.get(), expected.get());
+ ensure_equals_geometry(static_cast<const Geometry*>(result.get()), expected.get());
}
template<>
@@ -89,7 +89,7 @@ void object::test<3>()
auto expected = reader_.read("LINEARRING (0 0, 1 0, 1 1, 0 1, 0 0)");
- ensure_equals_geometry(result.get(), expected.get());
+ ensure_equals_geometry(static_cast<const Geometry*>(result.get()), expected.get());
}
template<>
@@ -106,7 +106,7 @@ void object::test<4>()
auto expected = reader_.read("LINESTRING (0 0, 1 1, 2 2)");
- ensure_equals_geometry(result.get(), expected.get());
+ ensure_equals_geometry(static_cast<const Geometry*>(result.get()), expected.get());
}
template<>
diff --git a/tests/unit/operation/polygonize/PolygonizeTest.cpp b/tests/unit/operation/polygonize/PolygonizeTest.cpp
index 970fd4797..99f110fea 100644
--- a/tests/unit/operation/polygonize/PolygonizeTest.cpp
+++ b/tests/unit/operation/polygonize/PolygonizeTest.cpp
@@ -137,7 +137,7 @@ struct test_polygonizetest_data {
}
switch(typ) {
- case ResultType::POLYGONS: retGeoms = asGeometry(polygonizer.getPolygons()); break;
+ case ResultType::POLYGONS: retGeoms = asGeometry(polygonizer.getSurfaces()); break;
case ResultType::CUT_EDGES: retGeoms = asGeometry(polygonizer.getCutEdges()); break;
case ResultType::DANGLES: retGeoms = asGeometry(polygonizer.getDangles()); break;
case ResultType::INVALID_RING_LINES: retGeoms = asGeometry(polygonizer.getInvalidRingLines()); break;
@@ -401,5 +401,63 @@ void object::test<11>()
POLYGONS);
}
+template<>
+template<>
+void object::test<12>()
+{
+ set_test_name("curved inputs");
+
+ std::vector<std::string> input{
+ "MULTICURVE ((10 0, 0 0, 0 10, 10 10), CIRCULARSTRING (10 10, 5 5, 10 0), COMPOUNDCURVE ((10 10, 20 10, 20 0, 10 0), CIRCULARSTRING (10 0, 15 5, 10 10)))"
+ };
+
+ std::vector<std::string> expected{
+ "CURVEPOLYGON (COMPOUNDCURVE ((0 0, 0 10, 10 10), CIRCULARSTRING (10 10, 5 5, 10 0), (10 0, 0 0)))",
+ "CURVEPOLYGON (CIRCULARSTRING (10 0, 5 5, 10 10, 15 5, 10 0))",
+ "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (10 0, 15 5, 10 10), (10 10, 20 10, 20 0, 10 0)))"
+ };
+
+ doTest(input, expected, false, POLYGONS);
+
+ // remove the hole
+ expected.erase(expected.begin() + 1);
+ doTest(input, expected, true, POLYGONS);
+}
+
+template<>
+template<>
+void object::test<13>()
+{
+ set_test_name("curved dangle");
+
+ doTest({"LINESTRING (0 0, 1 0, 1 1, 0 0)", "CIRCULARSTRING (1 1, 2 2, 3 1)"},
+ {"CIRCULARSTRING (1 1, 2 2, 3 1)"},
+ false,
+ DANGLES);
+}
+
+template<>
+template<>
+void object::test<14>()
+{
+ set_test_name("curved inputs requiring PIP test");
+
+ std::vector<std::string> input{
+ "COMPOUNDCURVE(CIRCULARSTRING (-4 -3, 0 5, 4 -3), (4 -3, -4 -3))",
+ "COMPOUNDCURVE((-4 -3, -1 0), CIRCULARSTRING (-1 0, 0 1, 1 0), (1 0, -4 -3))"
+ };
+
+ std::vector<std::string> expected{
+ "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-4 -3, 0 5, 4 -3), (4 -3, -4 -3)), COMPOUNDCURVE ((-4 -3, 1 0), CIRCULARSTRING (1 0, 0 1, -1 0), (-1 0, -4 -3)))",
+ "CURVEPOLYGON (COMPOUNDCURVE ((-4 -3, -1 0), CIRCULARSTRING (-1 0, 0 1, 1 0), (1 0, -4 -3)))"
+ };
+
+ doTest(input, expected, false, POLYGONS);
+
+ expected.erase(expected.begin() + 1);
+
+ doTest(input, expected, true, POLYGONS);
+}
+
} // namespace tut
commit 99696486a8c454550367941384225a3c9f95ce14
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Mar 31 16:41:23 2026 -0400
Add CurveBuilder
diff --git a/include/geos/geom/util/CurveBuilder.h b/include/geos/geom/util/CurveBuilder.h
new file mode 100644
index 000000000..dcd2416e3
--- /dev/null
+++ b/include/geos/geom/util/CurveBuilder.h
@@ -0,0 +1,65 @@
+/**********************************************************************
+*
+ * 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 Foundations.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/export.h>
+
+#include <memory>
+#include <vector>
+
+namespace geos::geom {
+class CoordinateSequence;
+class Curve;
+class GeometryFactory;
+class SimpleCurve;
+}
+
+namespace geos::geom::util {
+
+/// The CurveBuilder is a utility class to assist in construction of simple or
+/// compound curves, such as when combining coordinates from a list of Edges.
+class GEOS_DLL CurveBuilder {
+public:
+ CurveBuilder(const GeometryFactory& gfact, bool hasZ, bool hasM);
+
+ // Add all coordinates in the provided geometry
+ void add(const Curve& geom);
+
+ // Add all coordinates in the provided sequence
+ void add(const CoordinateSequence& seq, bool isCurved);
+
+ // Close the ring, if necessary, using a linear segment
+ void closeRing();
+
+ // Get the result geometry
+ std::unique_ptr<Curve> getGeometry();
+
+ // Get a reference to the CoordinateSequence to which
+ // coordinates are currently being added.
+ CoordinateSequence& getSeq(bool isCurved);
+
+private:
+ void finishCurve();
+ void finishLine();
+
+ std::vector<std::unique_ptr<SimpleCurve>> m_curves;
+ std::unique_ptr<CoordinateSequence> m_pts{nullptr};
+ const GeometryFactory& m_gfact;
+ const bool m_hasZ;
+ const bool m_hasM;
+ bool m_isCurved{false};
+};
+
+}
\ No newline at end of file
diff --git a/src/geom/util/CurveBuilder.cpp b/src/geom/util/CurveBuilder.cpp
new file mode 100644
index 000000000..314001e72
--- /dev/null
+++ b/src/geom/util/CurveBuilder.cpp
@@ -0,0 +1,148 @@
+/**********************************************************************
+*
+ * 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.
+ *
+ **********************************************************************/
+
+#include <geos/geom/util/CurveBuilder.h>
+
+#include <geos/geom/CircularString.h>
+#include <geos/geom/CompoundCurve.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/LineString.h>
+
+namespace geos::geom::util {
+
+CurveBuilder::CurveBuilder(const GeometryFactory& gfact, bool hasZ, bool hasM)
+ : m_gfact(gfact),
+ m_hasZ(hasZ),
+ m_hasM(hasM)
+{}
+
+void
+CurveBuilder::add(const Curve& geom)
+{
+ if (geom.getGeometryTypeId() == GEOS_COMPOUNDCURVE) {
+ const CompoundCurve& cc = static_cast<const CompoundCurve&>(geom);
+ for (std::size_t i = 0; i < cc.getNumCurves(); i++) {
+ add(*cc.getCurveN(i));
+ }
+ } else {
+ const CoordinateSequence* coords = static_cast<const SimpleCurve&>(geom).getCoordinatesRO();
+ const bool isCurved = geom.getGeometryTypeId() == GEOS_CIRCULARSTRING;
+ add(*coords, isCurved);
+ }
+}
+
+void
+CurveBuilder::add(const CoordinateSequence& coords, bool isCurved)
+{
+ getSeq(isCurved).add(*coords, false);
+}
+
+void
+CurveBuilder::closeRing()
+{
+ if (m_curves.empty() && (m_pts == nullptr || m_pts->isEmpty())) {
+ return;
+ }
+
+ CoordinateXYZM first;
+ if (!m_curves.empty()) {
+ m_curves.front()->getCoordinatesRO()->getAt(0, first);
+ } else {
+ m_pts->getAt(0, first);
+ }
+
+ CoordinateXYZM last;
+ if (m_pts && !m_pts->isEmpty()) {
+ m_pts->getAt(m_pts->size() - 1, last);
+ } else {
+ const auto* seq = m_curves.back()->getCoordinatesRO();
+ seq->getAt(seq->size() - 1, last);
+ }
+
+ if (first.equals2D(last)) {
+ return;
+ }
+
+ if (m_pts && m_isCurved) {
+ finishCurve();
+ getSeq(false).add(last);
+ }
+
+ getSeq(false).add(first);
+}
+
+void
+CurveBuilder::finishCurve()
+{
+ m_curves.push_back(m_gfact.createCircularString(std::move(m_pts)));
+ m_pts = nullptr;
+}
+
+void
+CurveBuilder::finishLine()
+{
+ m_curves.push_back(m_gfact.createLineString(std::move(m_pts)));
+ m_pts = nullptr;
+}
+
+std::unique_ptr<Curve>
+CurveBuilder::getGeometry()
+{
+ if (m_pts) {
+ if (m_isCurved) {
+ finishCurve();
+ } else {
+
+ if (m_curves.empty() && m_pts->isRing()) {
+ m_curves.push_back(m_gfact.createLinearRing(std::move(m_pts)));
+ } else {
+ finishLine();
+ }
+ }
+ }
+
+ if (m_curves.empty()) {
+ auto seq = std::make_unique<CoordinateSequence>(0, m_hasZ, m_hasM);
+ return m_gfact.createLineString(std::move(seq));
+ }
+
+ if (m_curves.size() == 1) {
+ return std::move(m_curves[0]);
+ }
+
+ return m_gfact.createCompoundCurve(std::move(m_curves));
+}
+
+CoordinateSequence&
+CurveBuilder::getSeq(bool isCurved)
+{
+ if (m_pts) {
+ if (m_isCurved && !isCurved) {
+ finishCurve();
+ } else if (isCurved && !m_isCurved) {
+ finishLine();
+ }
+ }
+
+ if (!m_pts) {
+ m_pts = std::make_unique<CoordinateSequence>(0, m_hasZ, m_hasM);
+ m_isCurved = isCurved;
+ }
+
+ return *m_pts;
+}
+
+}
diff --git a/tests/unit/geom/util/CurveBuilderTest.cpp b/tests/unit/geom/util/CurveBuilderTest.cpp
new file mode 100644
index 000000000..4276bd9ce
--- /dev/null
+++ b/tests/unit/geom/util/CurveBuilderTest.cpp
@@ -0,0 +1,159 @@
+// tut
+#include <tut/tut.hpp>
+#include <tut/tut_macros.hpp>
+// geos
+#include <geos/io/WKTReader.h>
+#include <geos/geom/CircularString.h>
+#include <geos/geom/util/CurveBuilder.h>
+#include <geos/util.h>
+
+#include <utility.h>
+// std
+#include <vector>
+
+using namespace geos::geom;
+using geos::geom::util::CurveBuilder;
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used by tests
+struct test_curvebuilder_data {
+ geos::io::WKTReader reader_;
+
+ GeometryFactory::Ptr factory_;
+ CurveBuilder builder_;
+
+ test_curvebuilder_data() :
+ factory_(GeometryFactory::create()),
+ builder_(*factory_, false, false)
+ {}
+
+ void add(const std::string& wkt) {
+ auto g = reader_.read<Curve>(wkt);
+ builder_.add(*g);
+ }
+
+};
+
+typedef test_group<test_curvebuilder_data> group;
+typedef group::object object;
+
+group test_curvebuilder_group("geos::geom::util::CurveBuilder");
+
+template<>
+template<>
+void object::test<1>()
+{
+ set_test_name("empty result");
+
+ CurveBuilder builder(*factory_, false, true);
+ auto result = builder.getGeometry();
+
+ ensure(result->isEmpty());
+ ensure_equals(result->getGeometryTypeId(), GEOS_LINESTRING);
+ ensure(!result->hasZ());
+ ensure(result->hasM());
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+ set_test_name("CompoundCurve result");
+
+ add("LINESTRING (-6 0, -5 0)");
+ add("CIRCULARSTRING (-5 0, 0 5, 5 0)");
+ add("LINESTRING (5 0, 6 0)");
+ add("CIRCULARSTRING (6 0, 0 6, -6 0)");
+
+ auto result = builder_.getGeometry();
+
+ auto expected = reader_.read("COMPOUNDCURVE ((-6 0, -5 0), CIRCULARSTRING (-5 0, 0 5, 5 0), (5 0, 6 0), CIRCULARSTRING (6 0, 0 6, -6 0))");
+
+ ensure_equals_geometry(result.get(), expected.get());
+}
+
+template<>
+template<>
+void object::test<3>()
+{
+ set_test_name("LinearRing result");
+
+ add("LINESTRING (0 0, 1 0, 1 1)");
+ add("LINESTRING (1 1, 0 1, 0 0)");
+
+ auto result = builder_.getGeometry();
+
+ auto expected = reader_.read("LINEARRING (0 0, 1 0, 1 1, 0 1, 0 0)");
+
+ ensure_equals_geometry(result.get(), expected.get());
+}
+
+template<>
+template<>
+void object::test<4>()
+{
+ set_test_name("empty component");
+
+ add("LINESTRING (0 0, 1 1)");
+ add("LINESTRING EMPTY");
+ add("LINESTRING (1 1, 2 2)");
+
+ auto result = builder_.getGeometry();
+
+ auto expected = reader_.read("LINESTRING (0 0, 1 1, 2 2)");
+
+ ensure_equals_geometry(result.get(), expected.get());
+}
+
+template<>
+template<>
+void object::test<5>()
+{
+ set_test_name("disjoint curves");
+
+ add("LINESTRING (-100 0, -50 0)");
+ add("CIRCULARSTRING (0 0, 1 1, 2 0)");
+
+ ensure_THROW(builder_.getGeometry(), geos::util::IllegalArgumentException);
+}
+
+template<>
+template<>
+void object::test<6>()
+{
+ set_test_name("closeRing after arc");
+
+ auto cs = reader_.read<CircularString>("CIRCULARSTRING (0 0, 1 1, 2 0)");
+ builder_.add(*cs);
+ builder_.closeRing();
+
+ auto result = builder_.getGeometry();
+
+ auto expected = reader_.read("COMPOUNDCURVE(CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0))");
+
+ ensure_equals_geometry(static_cast<const Geometry*>(result.get()), expected.get());
+
+}
+
+template<>
+template<>
+void object::test<7>()
+{
+ set_test_name("closeRing after line");
+
+ auto cs = reader_.read<LineString>("LINESTRING (0 0, 1 1, 2 0)");
+ builder_.add(*cs);
+ builder_.closeRing();
+
+ auto result = builder_.getGeometry();
+
+ auto expected = reader_.read("LINEARRING (0 0, 1 1, 2 0, 0 0)");
+
+ ensure_equals_geometry(static_cast<const Geometry*>(result.get()), expected.get());
+}
+
+} // namespace tut
commit dfa7c7ad43e5db1cd4ef5903ea3bf26070936faa
Author: Daniel Baston <dbaston at gmail.com>
Date: Wed Apr 1 11:52:01 2026 -0400
IndexedPointInAreaLocator: guard against curved inputs
diff --git a/src/algorithm/locate/IndexedPointInAreaLocator.cpp b/src/algorithm/locate/IndexedPointInAreaLocator.cpp
index ce9ac78e4..45b135d9a 100644
--- a/src/algorithm/locate/IndexedPointInAreaLocator.cpp
+++ b/src/algorithm/locate/IndexedPointInAreaLocator.cpp
@@ -99,6 +99,7 @@ IndexedPointInAreaLocator::buildIndex(const geom::Geometry& g)
IndexedPointInAreaLocator::IndexedPointInAreaLocator(const geom::Geometry& g)
: areaGeom(g)
{
+ util::ensureNoCurvedComponents(g);
}
geom::Location
commit d1a9893c4c3a9867a2149d49a9f21c6331703bd2
Author: Daniel Baston <dbaston at gmail.com>
Date: Fri Mar 27 11:30:28 2026 -0400
CircularArcs: add getDirectionPoint
diff --git a/include/geos/algorithm/CircularArcs.h b/include/geos/algorithm/CircularArcs.h
index 60876b97e..e1d72ef91 100644
--- a/include/geos/algorithm/CircularArcs.h
+++ b/include/geos/algorithm/CircularArcs.h
@@ -32,6 +32,8 @@ public:
static double getAngle(const geom::CoordinateXY& pt, const geom::CoordinateXY& center);
+ static geom::CoordinateXY getDirectionPoint(const geom::CoordinateXY& center, double radius, double theta, bool isCCW);
+
static double getMidpointAngle(double theta0, double theta2, bool isCCW);
static geom::CoordinateXY getMidpoint(const geom::CoordinateXY& p0, const geom::CoordinateXY& p2, const geom::CoordinateXY& center, double radius, bool isCCW);
diff --git a/include/geos/geom/CircularArc.h b/include/geos/geom/CircularArc.h
index de75334fc..bb3d3d2c2 100644
--- a/include/geos/geom/CircularArc.h
+++ b/include/geos/geom/CircularArc.h
@@ -105,6 +105,11 @@ public:
return m_pos;
}
+ /// Computes a direction point suitable for ordering arcs with the same
+ /// origin point. The direction point is located at a distance R along the
+ /// line tangent to the arc at the origin point.
+ CoordinateXY getDirectionPoint() const;
+
Envelope getEnvelope() const;
/// Return the length of the arc
diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp
index 55d7a433a..aee3b7b5d 100644
--- a/src/algorithm/CircularArcs.cpp
+++ b/src/algorithm/CircularArcs.cpp
@@ -39,6 +39,18 @@ CircularArcs::getAngle(const CoordinateXY& p, const CoordinateXY& center)
return std::atan2(p.y - center.y, p.x - center.x);
}
+CoordinateXY
+CircularArcs::getDirectionPoint(const geom::CoordinateXY& center, double radius, double theta, bool isCCW)
+{
+ const double dt = geos::MATH_PI / 4 * (isCCW ? 1: -1);
+
+ // To get a point that is exactly on the tangent to this arc at p0, we would create a point
+ // at radius * sqrt(2). During overlay, this can produce a situation where noded edges originating
+ // from the same point have direction points that are in the same direction. Nudging the direction point
+ // slightly in the direction of the arc resolves this issue.
+ return createPoint(center, radius * std::sqrt(2) * (1 - 1e-6), theta + dt);
+}
+
double
CircularArcs::getMidpointAngle(double theta0, double theta2, bool isCCW)
{
diff --git a/src/geom/CircularArc.cpp b/src/geom/CircularArc.cpp
index 1ec27bbe6..5ac8f2980 100644
--- a/src/geom/CircularArc.cpp
+++ b/src/geom/CircularArc.cpp
@@ -276,6 +276,11 @@ CircularArc::getArea() const {
return R*R/2*(theta - std::sin(theta));
}
+CoordinateXY
+CircularArc::getDirectionPoint() const {
+ return algorithm::CircularArcs::getDirectionPoint(getCenter(), getRadius(), theta0(), getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE);
+}
+
Envelope
CircularArc::getEnvelope() const {
Envelope env;
diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp
index ccb3e4979..d1296a32f 100644
--- a/tests/unit/algorithm/CircularArcsTest.cpp
+++ b/tests/unit/algorithm/CircularArcsTest.cpp
@@ -3,11 +3,13 @@
#include <geos/geom/Coordinate.h>
#include <geos/algorithm/CircularArcs.h>
#include <geos/geom/CircularArc.h>
+#include <geos/geom/Quadrant.h>
using geos::geom::CircularArc;
using geos::geom::CoordinateXY;
using geos::algorithm::CircularArcs;
using geos::geom::Envelope;
+using geos::geom::Quadrant;
using geos::MATH_PI;
namespace tut {
@@ -305,5 +307,26 @@ 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("getDirectionPoint");
+
+ const CircularArc arc1 = CircularArc::create(XY{-4, 3}, XY{0, -5}, XY{-4, -3});
+ const auto dp1 = arc1.getDirectionPoint();
+ ensure_equals(Quadrant::quadrant(arc1.p0(), dp1), 0); // NE
+ ensure_equals("dp1.x", dp1.x, -1, 1e-5);
+ ensure_equals("dp1.y", dp1.y, 7, 1e-5);
+ ensure_equals("point is not expected distance from p0", arc1.p0().distance(dp1), arc1.getRadius(), 1e-5);
+
+
+ const auto arc2 = arc1.reverse();
+ const auto dp2 = arc2.getDirectionPoint();
+ ensure_equals(Quadrant::quadrant(arc2.p0(), dp2), 3); // SE
+ ensure_equals("dp2.x", dp2.x, -1, 1e-5);
+ ensure_equals("dp2.y", dp2.y, -7, 1e-5);
+ ensure_equals("point is not expected distance from p0", arc2.p0().distance(dp2), arc2.getRadius(), 1e-5);
}
+
+}
-----------------------------------------------------------------------
Summary of changes:
capi/geos_c.h.in | 16 +-
capi/geos_ts_c.cpp | 66 +++++---
include/geos/algorithm/CircularArcs.h | 2 +
include/geos/geom/CircularArc.h | 5 +
include/geos/geom/util/CurveBuilder.h | 65 ++++++++
include/geos/operation/polygonize/EdgeRing.h | 55 ++-----
include/geos/operation/polygonize/PolygonizeEdge.h | 8 +-
.../geos/operation/polygonize/PolygonizeGraph.h | 21 ++-
include/geos/operation/polygonize/Polygonizer.h | 66 ++++----
src/algorithm/CircularArcs.cpp | 12 ++
src/algorithm/locate/IndexedPointInAreaLocator.cpp | 1 +
src/geom/CircularArc.cpp | 5 +
src/geom/util/CurveBuilder.cpp | 148 ++++++++++++++++++
src/operation/polygonize/EdgeRing.cpp | 172 +++++++++++++--------
src/operation/polygonize/PolygonizeEdge.cpp | 4 +-
src/operation/polygonize/PolygonizeGraph.cpp | 49 +++++-
src/operation/polygonize/Polygonizer.cpp | 106 +++++++------
tests/unit/algorithm/CircularArcsTest.cpp | 23 +++
tests/unit/capi/GEOSBuildAreaTest.cpp | 7 +-
tests/unit/capi/GEOSPolygonizeTest.cpp | 123 ++++++++++++---
tests/unit/geom/util/CurveBuilderTest.cpp | 159 +++++++++++++++++++
tests/unit/operation/polygonize/PolygonizeTest.cpp | 60 ++++++-
22 files changed, 931 insertions(+), 242 deletions(-)
create mode 100644 include/geos/geom/util/CurveBuilder.h
create mode 100644 src/geom/util/CurveBuilder.cpp
create mode 100644 tests/unit/geom/util/CurveBuilderTest.cpp
hooks/post-receive
--
GEOS
More information about the geos-commits
mailing list