[geos-commits] [SCM] GEOS branch main updated. cad26ad980e9c7e15e7286122abcf091c37aa398
git at osgeo.org
git at osgeo.org
Fri Dec 5 10:47:08 PST 2025
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 cad26ad980e9c7e15e7286122abcf091c37aa398 (commit)
from bf3be9ebb55f2912a19acc54adeaae68c7fd01dd (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 cad26ad980e9c7e15e7286122abcf091c37aa398
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date: Fri Dec 5 10:46:04 2025 -0800
Return EMPTY components when repeated point removal renders the underlying parts (rings, lines, etc) invalid (0 dimensional), closes #1326 and #1293
diff --git a/include/geos/io/WKTReader.h b/include/geos/io/WKTReader.h
index 7294d64f5..9fc86c978 100644
--- a/include/geos/io/WKTReader.h
+++ b/include/geos/io/WKTReader.h
@@ -115,6 +115,7 @@ public:
}
std::unique_ptr<geom::Geometry> read(const std::string& wellKnownText) const;
+ std::unique_ptr<geom::CoordinateSequence> readCoordinates(const std::string& wellKnownText) const;
protected:
std::unique_ptr<geom::CoordinateSequence> getCoordinates(io::StringTokenizer* tokenizer, OrdinateSet& ordinates) const;
diff --git a/include/geos/operation/valid/RepeatedPointRemover.h b/include/geos/operation/valid/RepeatedPointRemover.h
index e6e91c24c..d70e71802 100644
--- a/include/geos/operation/valid/RepeatedPointRemover.h
+++ b/include/geos/operation/valid/RepeatedPointRemover.h
@@ -48,6 +48,13 @@ namespace valid {
const geom::CoordinateSequence* seq,
double tolerance = 0.0);
+ static std::unique_ptr<geom::CoordinateSequence>
+ removeRepeatedOrInvalidPoints(
+ const geom::CoordinateSequence* seq,
+ double tolerance = 0.0) {
+ return removeRepeatedAndInvalidPoints(seq, tolerance);
+ };
+
static std::unique_ptr<geom::Geometry>
removeRepeatedPoints(
const geom::Geometry* geom,
diff --git a/src/coverage/CoverageRingEdges.cpp b/src/coverage/CoverageRingEdges.cpp
index 4d1495a47..a5a697cc1 100644
--- a/src/coverage/CoverageRingEdges.cpp
+++ b/src/coverage/CoverageRingEdges.cpp
@@ -140,7 +140,8 @@ CoverageRingEdges::extractRingEdges(
std::unique_ptr<CoordinateSequence> pts
= RepeatedPointRemover::removeRepeatedPoints( ring->getCoordinatesRO() );
std::vector<CoverageEdge*> ringEdges;
- //-- if compacted ring is too short, don't process it
+ // if compacted ring is too short, don't process it,
+ // just return empty vector
if (pts->getSize() < 3)
return ringEdges;
diff --git a/src/io/WKTReader.cpp b/src/io/WKTReader.cpp
index 2b4400a6b..3c1d6fa69 100644
--- a/src/io/WKTReader.cpp
+++ b/src/io/WKTReader.cpp
@@ -65,6 +65,23 @@ WKTReader::read(const std::string& wellKnownText) const
return ret;
}
+
+std::unique_ptr<CoordinateSequence>
+WKTReader::readCoordinates(const std::string& wellKnownText) const
+{
+ CLocalizer clocale;
+ StringTokenizer tokenizer(wellKnownText);
+ OrdinateSet ordinateFlags = OrdinateSet::createXY();
+ auto ret = getCoordinates(&tokenizer, ordinateFlags);
+
+ if (tokenizer.peekNextToken() != StringTokenizer::TT_EOF) {
+ tokenizer.nextToken();
+ throw ParseException("Unexpected text after end of geometry");
+ }
+
+ return ret;
+}
+
std::unique_ptr<CoordinateSequence>
WKTReader::getCoordinates(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
{
diff --git a/src/operation/valid/RepeatedPointRemover.cpp b/src/operation/valid/RepeatedPointRemover.cpp
index 22b4a9aa2..aea5e320b 100644
--- a/src/operation/valid/RepeatedPointRemover.cpp
+++ b/src/operation/valid/RepeatedPointRemover.cpp
@@ -200,19 +200,21 @@ public:
auto filtCoords = filter.getCoords();
if (filtCoords->size() == 0) return nullptr;
+ if (filtCoords->size() == 1) return nullptr;
// End points for comparison and sequence repair
const Coordinate& origEndCoord = coordinates->back();
+ bool origEndCoordIsValid = origEndCoord.isValid();
// Fluff up overly small filtered outputs
- if(filtCoords->size() < minLength) {
+ if(filtCoords->size() < minLength && origEndCoordIsValid) {
filtCoords->add(origEndCoord);
}
const Coordinate& filtEndCoord = filtCoords->back();
// We stripped the last point, let's put it back on
- if (!origEndCoord.equals2D(filtEndCoord)) {
+ if (origEndCoordIsValid && !origEndCoord.equals2D(filtEndCoord)) {
// If the end of the filtered coordinates is within
// tolerance of the original end, we drop the last filtered
// coordinate so the output still follows the tolerance rule
@@ -223,8 +225,14 @@ public:
filtCoords->add(origEndCoord);
}
- return filtCoords;
+ if (filtCoords->size() <= 1) return nullptr;
+ else return filtCoords;
};
+
+
+
+
+
}; // RepeatedPointCoordinateOperation
diff --git a/tests/unit/capi/GEOSRemoveRepeatedPointsTest.cpp b/tests/unit/capi/GEOSRemoveRepeatedPointsTest.cpp
index 0b78590b2..c1a53bb32 100644
--- a/tests/unit/capi/GEOSRemoveRepeatedPointsTest.cpp
+++ b/tests/unit/capi/GEOSRemoveRepeatedPointsTest.cpp
@@ -32,9 +32,9 @@ void object::test<1>
()
{
geom1_ = GEOSGeomFromWKT("POLYGON((0 0, 0 1, 0 10, 10 10, 10 0, 9 0, 1 0, 0 0))");
- geom2_ = GEOSRemoveRepeatedPoints(geom1_, 3.0);
+ result_ = GEOSRemoveRepeatedPoints(geom1_, 3.0);
expected_ = GEOSGeomFromWKT("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))");
- ensure_geometry_equals(geom2_, expected_);
+ ensure_geometry_equals(result_, expected_);
}
// https://github.com/libgeos/geos/issues/759
@@ -44,9 +44,9 @@ void object::test<2>
()
{
geom1_ = GEOSGeomFromWKT("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))");
+ expected_ = GEOSGeomFromWKT("POLYGON EMPTY");
result_ = GEOSRemoveRepeatedPoints(geom1_, 2.0);
-
- ensure(result_ == nullptr);
+ ensure_geometry_equals(result_, expected_);
}
template<>
@@ -57,7 +57,6 @@ void object::test<3>()
ensure(input_);
result_ = GEOSRemoveRepeatedPoints(input_, 0);
-
ensure("curved geometry not supported", result_ == nullptr);
}
diff --git a/tests/unit/operation/valid/RepeatedPointRemoverTest.cpp b/tests/unit/operation/valid/RepeatedPointRemoverTest.cpp
index 205eefcc9..5d12c1b35 100644
--- a/tests/unit/operation/valid/RepeatedPointRemoverTest.cpp
+++ b/tests/unit/operation/valid/RepeatedPointRemoverTest.cpp
@@ -44,6 +44,19 @@ struct test_repeated_point_remover_test_data
return writer.write(&geom);
}
+ void
+ checkSimpleSequence(const std::string& input, const std::string& expected, double tolerance = 0.0)
+ {
+ std::unique_ptr<CoordinateSequence> inCoords = reader.readCoordinates(input);
+ std::unique_ptr<CoordinateSequence> exCoords = reader.readCoordinates(expected);
+ auto outCoords = RepeatedPointRemover::removeRepeatedPoints(inCoords.get(), tolerance);
+ // std::cout << "output" << std::endl;
+ // std::cout << outCoords->toString() << std::endl;
+ // std::cout << "expected" << std::endl;
+ // std::cout << exCoords->toString() << std::endl;
+ ensure_equals(*outCoords, *exCoords);
+ }
+
void
checkSequence(const std::string& input, const std::string& expected, double tolerance = 0.0)
{
@@ -58,6 +71,10 @@ struct test_repeated_point_remover_test_data
ensure_equals("hasZ", exCoords->hasZ(), outCoords->hasZ());
ensure_equals("hasM", exCoords->hasM(), outCoords->hasM());
+ // std::cout << "output" << std::endl;
+ // std::cout << outCoords->toString() << std::endl;
+ // std::cout << "expected" << std::endl;
+ // std::cout << exCoords->toString() << std::endl;
ensure_equals(*outCoords, *exCoords);
}
@@ -75,7 +92,6 @@ struct test_repeated_point_remover_test_data
ensure_equals_geometry(outGeom.get(), exGeom.get());
}
-
};
typedef test_group<test_repeated_point_remover_test_data> group;
@@ -87,9 +103,9 @@ template<>
template<>
void object::test<1>()
{
- checkSequence(
- "LINESTRING (3 7, 8 8, 8 8, 8 8, 10 9)",
- "LINESTRING (3 7, 8 8, 10 9)",
+ checkSimpleSequence(
+ "(3 7, 8 8, 8 8, 8 8, 10 9)",
+ "(3 7, 8 8, 10 9)",
0.0
);
}
@@ -98,24 +114,28 @@ template<>
template<>
void object::test<2>()
{
- checkSequence(
- "LINESTRING (3 7, 8 8, 8 8, 8 8)",
- "LINESTRING (3 7, 8 8)",
+ checkSimpleSequence(
+ "(3 7, 8 8, 8 8, 8 8)",
+ "(3 7, 8 8)",
0.0
);
}
+// CoordinateSequences just retain each coordinate within
+// the filter tolerance
template<>
template<>
void object::test<3>()
{
- checkSequence(
- "LINESTRING (0 0, 1 0, 4 0, 5 0)",
- "LINESTRING (0 0, 4 0)",
+ checkSimpleSequence(
+ "(0 0, 1 0, 4 0, 5 0)",
+ "(0 0, 4 0)",
3.0
);
}
+// Linestrings note the last point and somehow
+// retain it in preference over the internal point
template<>
template<>
void object::test<4>()
@@ -123,7 +143,7 @@ void object::test<4>()
checkGeometry(
"LINESTRING (0 0, 1 0, 4 0, 5 0)",
"LINESTRING (0 0, 5 0)",
- 3.0
+ 3
);
}
@@ -140,7 +160,7 @@ void object::test<5>()
}
-// Dimension is preserved
+// Dimension is preserved during reduction
template<>
template<>
void object::test<6>()
@@ -151,6 +171,141 @@ void object::test<6>()
}
+// Removing from a sequence with enough tolerance
+// results in single-entry sequence
+template<>
+template<>
+void object::test<7>()
+{
+ checkSimpleSequence(
+ "(3 7, 3 7, 3 7, 3 7)",
+ "(3 7)",
+ 0.0
+ );
+}
+
+// Removing from a sequence with enough tolerance
+// results in single-entry sequence
+template<>
+template<>
+void object::test<8>()
+{
+ checkSimpleSequence(
+ "(3 7, 3.1 7.1, 3.2 7.2, 3.3 7.3)",
+ "(3 7)",
+ 1.0
+ );
+}
+
+
+template<>
+template<>
+void object::test<9>()
+{
+ checkGeometry(
+ "LINESTRING (0 0, 0 1, 0 2, 0 3)",
+ "LINESTRING EMPTY",
+ 14.0
+ );
+}
+
+
+// small hole should collapse away
+template<>
+template<>
+void object::test<10>()
+{
+ checkGeometry(
+ "POLYGON ((0 0, 9 0, 10 0, 10 10, 0 10, 0 1, 0 0), (5 5, 5 6, 6 6, 6 5, 5 5))",
+ "POLYGON ((0 0, 9 0, 10 10, 0 10, 0 0))",
+ 3.0
+ );
+}
+
+// small exterior ring should disappear whole polygon
+template<>
+template<>
+void object::test<11>()
+{
+ checkGeometry(
+ "POLYGON ((0 0, 9 0, 10 0, 10 10, 0 10, 0 1, 0 0))",
+ "POLYGON ((0 0, 10 10, 0 0))",
+ 12.0
+ );
+}
+
+template<>
+template<>
+void object::test<12>()
+{
+ checkGeometry(
+ "POLYGON ((0 0, 9 0, 10 0, 10 10, 0 10, 0 1, 0 0))",
+ "POLYGON EMPTY",
+ 22.0
+ );
+}
+
+// Careful not to replace invalid coordinates
+template<>
+template<>
+void object::test<13>()
+{
+ checkGeometry(
+ "LINESTRING (0 0, 0 Inf, 1 1, Inf 0)",
+ "LINESTRING (0 0, 1 1)",
+ 1.0
+ );
+}
+
+// If it filters down to just one point, it should be empty
+template<>
+template<>
+void object::test<14>()
+{
+ checkGeometry(
+ "LINESTRING (0 0, 0 Inf, 1 1)",
+ "LINESTRING EMPTY",
+ 2.0
+ );
+}
+
+// Filter out invalid coordinate, even at start/ends
+template<>
+template<>
+void object::test<15>()
+{
+ checkGeometry(
+ "POLYGON ((Inf Inf, 0 0, 10 0, 10 10, 0 10, 0 0, Inf Inf))",
+ "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))",
+ 2.0
+ );
+}
+
+// If it filters down to just one point, it should be empty
+template<>
+template<>
+void object::test<16>()
+{
+ checkGeometry(
+ "POLYGON ((Inf Inf, 0 0, 10 0, 10 10, 0 10, 0 0, Inf Inf))",
+ "POLYGON EMPTY",
+ 22.0
+ );
+}
+
+// https://github.com/libgeos/geos/issues/1293
+// Hole collapses, should not error out.
+template<>
+template<>
+void object::test<17>()
+{
+ checkGeometry(
+ "POLYGON ((139770.26822331376024522 188334.00010800323798321, 139769.5 188338.01162790699163452, 139769.5 188338.3723930635896977, 139769.5 188338.5, 139769.81343283582828008 188338.5, 139770.375 188339.375, 139772.39924806414637715 188340.26989983080420643, 139770.26822331376024522 188334.00010800323798321),(139769.75256541155977175 188338.40516005983226933, 139769.75256541153066792 188338.40516005983226933, 139769.75256541153066792 188338.4051600598031655, 139769.75256541155977175 188338.40516005983226933))",
+ "POLYGON ((139769.5 188338.011627907, 139769.5 188338.3723930636, 139769.5 188338.5, 139769.81343283583 188338.5, 139770.375 188339.375, 139772.39924806415 188340.2698998308, 139770.26822331376 188334.00010800324, 139769.5 188338.011627907))",
+ 1e-8
+ );
+}
+
} // namespace tut
-----------------------------------------------------------------------
Summary of changes:
include/geos/io/WKTReader.h | 1 +
.../geos/operation/valid/RepeatedPointRemover.h | 7 +
src/coverage/CoverageRingEdges.cpp | 3 +-
src/io/WKTReader.cpp | 17 ++
src/operation/valid/RepeatedPointRemover.cpp | 14 +-
tests/unit/capi/GEOSRemoveRepeatedPointsTest.cpp | 9 +-
.../operation/valid/RepeatedPointRemoverTest.cpp | 179 +++++++++++++++++++--
7 files changed, 209 insertions(+), 21 deletions(-)
hooks/post-receive
--
GEOS
More information about the geos-commits
mailing list