[geos-commits] [SCM] GEOS branch main updated. 62b3524601c63abb662ee7c8cae428c24fe92af0
git at osgeo.org
git at osgeo.org
Wed Mar 11 08:41:33 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 62b3524601c63abb662ee7c8cae428c24fe92af0 (commit)
via 8abf06184f336f7c5524445a060412d556bb1b36 (commit)
via c7f42e65c04579decfeb87af05acf6f4b3c3fdfc (commit)
via 5811106e467c051e4f616d686dc96a372d637c57 (commit)
via c89ef8e0f371ca82387f821b011c64a02012cba4 (commit)
via 377631cf28007b1d59649b9cb6dcd8bf23083fcc (commit)
from d5977ebb4e29d6d970a3f784836a46796526f1fa (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 62b3524601c63abb662ee7c8cae428c24fe92af0
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Mar 10 14:08:45 2026 -0400
LineString: Simplify implementation of normalize()
Since the addition of normalizeClosed() in
a807833875976a29fbcf509297f4a7a5f837aa09, we are guaranteed that
the first and last points differ. The need for normalization
can therefore be determined without looping over the points.
diff --git a/src/geom/LineString.cpp b/src/geom/LineString.cpp
index b3f1ceb26..c05932754 100644
--- a/src/geom/LineString.cpp
+++ b/src/geom/LineString.cpp
@@ -131,19 +131,12 @@ LineString::normalize()
normalizeClosed();
return;
}
- std::size_t npts = points->getSize();
- std::size_t n = npts / 2;
- for (std::size_t i = 0; i < n; i++) {
- std::size_t j = npts - 1 - i;
- if (!(points->getAt<CoordinateXY>(i) == points->getAt<CoordinateXY>(j))) {
- if (points->getAt<CoordinateXY>(i).compareTo(points->getAt<CoordinateXY>(j)) > 0) {
- if (points.use_count() > 1) {
- points = points->clone();
- }
- const_cast<CoordinateSequence*>(points.get())->reverse();
- }
- return;
+
+ if (points->front<CoordinateXY>().compareTo( points->back<CoordinateXY>()) == 1) {
+ if (points.use_count() > 1) {
+ points = points->clone();
}
+ const_cast<CoordinateSequence*>(points.get())->reverse();
}
}
commit 8abf06184f336f7c5524445a060412d556bb1b36
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Mar 10 13:29:05 2026 -0400
CurvePolygon: Support normalize()
diff --git a/include/geos/geom/Curve.h b/include/geos/geom/Curve.h
index d07a9d375..f9968fb9e 100644
--- a/include/geos/geom/Curve.h
+++ b/include/geos/geom/Curve.h
@@ -74,6 +74,8 @@ public:
virtual const SimpleCurve* getCurveN(std::size_t) const = 0;
+ std::unique_ptr<Curve> reverse() const;
+
protected:
Curve(const GeometryFactory& factory) : Geometry(&factory) {}
diff --git a/src/geom/Curve.cpp b/src/geom/Curve.cpp
index 91fd60da2..a55678267 100644
--- a/src/geom/Curve.cpp
+++ b/src/geom/Curve.cpp
@@ -54,6 +54,12 @@ Curve::isRing() const
return isClosed() && isSimple();
}
+std::unique_ptr<Curve>
+Curve::reverse() const
+{
+ return std::unique_ptr<Curve>(static_cast<Curve*>(Geometry::reverse().release()));
+}
+
}
}
diff --git a/src/geom/CurvePolygon.cpp b/src/geom/CurvePolygon.cpp
index 7c181a13a..7f07e9f41 100644
--- a/src/geom/CurvePolygon.cpp
+++ b/src/geom/CurvePolygon.cpp
@@ -81,8 +81,16 @@ namespace geom {
}
void
- CurvePolygon::normalize() {
- throw util::UnsupportedOperationException();
+ CurvePolygon::normalize()
+ {
+ shell->normalize();
+ for(auto& lr : holes) {
+ lr->normalize();
+ lr = lr->reverse();
+ }
+ std::sort(holes.begin(), holes.end(), [](const auto& a, const auto& b) {
+ return a->compareTo(b.get()) > 0;
+ });
}
double CurvePolygon::getArea() const {
diff --git a/tests/unit/geom/CurvePolygonTest.cpp b/tests/unit/geom/CurvePolygonTest.cpp
index ff638685c..0e60aded8 100644
--- a/tests/unit/geom/CurvePolygonTest.cpp
+++ b/tests/unit/geom/CurvePolygonTest.cpp
@@ -144,6 +144,8 @@ template<>
template<>
void object::test<3>()
{
+ set_test_name("operations");
+
// Predicates
ensure_THROW(cp_->contains(cp_.get()), geos::util::UnsupportedOperationException);
ensure_THROW(cp_->coveredBy(cp_.get()), geos::util::UnsupportedOperationException);
@@ -196,7 +198,6 @@ void object::test<3>()
"COMPOUNDCURVE ((0 0, 1 4, 4 5, 4 3), CIRCULARSTRING (4 3, 2 3, 2 1, 2 0, 0 0)), "
"CIRCULARSTRING (1.7 1, 1.6 0.5, 1.6 0.4, 1.4 0.4, 1.7 1))").get()));
auto cc3 = cp_->reverse();
- ensure_THROW(cc3->normalize(), geos::util::UnsupportedOperationException);
}
template<>
@@ -245,9 +246,36 @@ void object::test<5>()
std::vector<std::unique_ptr<geos::geom::Curve>> holes;
holes.push_back(std::move(cc));
- // hole is non-cloesd
+ // hole is non-closed
ensure_THROW(factory_->createCurvePolygon(std::move(shell), std::move(holes)), geos::util::IllegalArgumentException);
}
+template<>
+template<>
+void object::test<6>()
+{
+ set_test_name("normalize");
+
+ auto input = wktreader_.read(
+ "CURVEPOLYGON ("
+ "COMPOUNDCURVE ((10 10, 10 0, 0 0, 0 10), CIRCULARSTRING (0 10, 5 15, 10 10)),"
+ "(6 5, 6 6, 5 6, 5 5, 6 5),"
+ "CIRCULARSTRING (7 7, 8 8, 9 7, 8 6, 7 7),"
+ "CIRCULARSTRING (5 3, 4 2, 3 3, 4 4, 5 3))");
+
+ // ordering of holes is not strictly spatial, because we first sort on
+ // geometry type
+ auto expected = wktreader_.read(
+ "CURVEPOLYGON ("
+ "COMPOUNDCURVE ((0 0, 0 10), CIRCULARSTRING (0 10, 5 15, 10 10), (10 10, 10 0, 0 0)), "
+ "CIRCULARSTRING (7 7, 8 6, 9 7, 8 8, 7 7), "
+ "CIRCULARSTRING (3 3, 4 2, 5 3, 4 4, 3 3), "
+ "(5 5, 6 5, 6 6, 5 6, 5 5))");
+
+ auto result = input->clone();
+ result->normalize();
+
+ ensure_equals_exact_geometry_xyzm(result.get(), expected.get(), 0);
+}
}
diff --git a/tests/unit/geom/MultiSurfaceTest.cpp b/tests/unit/geom/MultiSurfaceTest.cpp
index 9d422c961..1b1d3509a 100644
--- a/tests/unit/geom/MultiSurfaceTest.cpp
+++ b/tests/unit/geom/MultiSurfaceTest.cpp
@@ -120,6 +120,8 @@ template<>
template<>
void object::test<3>()
{
+ set_test_name("operations");
+
// Predicates
ensure_THROW(ms_->contains(ms_.get()), geos::util::UnsupportedOperationException);
ensure_THROW(ms_->coveredBy(ms_.get()), geos::util::UnsupportedOperationException);
@@ -170,7 +172,6 @@ void object::test<3>()
"MULTISURFACE (((0 0, 0 1, 1 1, 1 0, 0 0)), "
"CURVEPOLYGON (CIRCULARSTRING (10 10, 11 9, 12 10, 11 11, 10 10)))").get()));
auto cc3 = ms_->reverse();
- ensure_THROW(cc3->normalize(), geos::util::UnsupportedOperationException);
}
}
commit c7f42e65c04579decfeb87af05acf6f4b3c3fdfc
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Mar 10 13:27:41 2026 -0400
CircularString: fix incorrect sort index
diff --git a/include/geos/geom/CircularString.h b/include/geos/geom/CircularString.h
index 54089f3bc..a2f0e5afb 100644
--- a/include/geos/geom/CircularString.h
+++ b/include/geos/geom/CircularString.h
@@ -79,7 +79,7 @@ protected:
int
getSortIndex() const override
{
- return SORTINDEX_LINESTRING;
+ return SORTINDEX_CIRCULARSTRING;
};
CircularString* reverseImpl() const override;
commit 5811106e467c051e4f616d686dc96a372d637c57
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Mar 10 12:03:32 2026 -0400
CompoundCurve: Support normalize()
diff --git a/include/geos/geom/CompoundCurve.h b/include/geos/geom/CompoundCurve.h
index c685be18c..ae22670e8 100644
--- a/include/geos/geom/CompoundCurve.h
+++ b/include/geos/geom/CompoundCurve.h
@@ -119,6 +119,10 @@ protected:
CompoundCurve* reverseImpl() const override;
private:
+ void normalizeClosed();
+
+ void reverseInPlace();
+
std::vector<std::unique_ptr<SimpleCurve>> curves;
Envelope envelope;
};
diff --git a/include/geos/geom/SimpleCurve.h b/include/geos/geom/SimpleCurve.h
index 2c7e4c8f4..6fe96b40c 100644
--- a/include/geos/geom/SimpleCurve.h
+++ b/include/geos/geom/SimpleCurve.h
@@ -101,6 +101,8 @@ public:
bool isEmpty() const override;
+ std::unique_ptr<SimpleCurve> reverse() const;
+
protected:
SimpleCurve(const SimpleCurve& other);
diff --git a/include/geos/operation/split/SplitGeometryAtVertex.h b/include/geos/operation/split/SplitGeometryAtVertex.h
new file mode 100644
index 000000000..cfee8f817
--- /dev/null
+++ b/include/geos/operation/split/SplitGeometryAtVertex.h
@@ -0,0 +1,43 @@
+/**********************************************************************
+*
+ * 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 <geos/export.h>
+#include <memory>
+#include <utility>
+
+// Forward declarations
+namespace geos::geom {
+class CircularString;
+class LineString;
+class SimpleCurve;
+}
+
+namespace geos::operation::split {
+
+class GEOS_DLL SplitGeometryAtVertex {
+
+public:
+ static std::pair<std::unique_ptr<geom::SimpleCurve>, std::unique_ptr<geom::SimpleCurve>>
+ splitSimpleCurveAtVertex(const geom::SimpleCurve& sc, std::size_t i);
+
+ static std::pair<std::unique_ptr<geom::LineString>, std::unique_ptr<geom::LineString>>
+ splitLineStringAtVertex(const geom::LineString& ls, std::size_t i);
+
+ static std::pair<std::unique_ptr<geom::CircularString>, std::unique_ptr<geom::CircularString>>
+ splitCircularStringAtVertex(const geom::CircularString& cs, std::size_t i);
+};
+
+}
diff --git a/src/geom/CompoundCurve.cpp b/src/geom/CompoundCurve.cpp
index 8897109c3..d2e268f9b 100644
--- a/src/geom/CompoundCurve.cpp
+++ b/src/geom/CompoundCurve.cpp
@@ -3,7 +3,7 @@
* GEOS - Geometry Engine Open Source
* http://geos.osgeo.org
*
- * Copyright (C) 2024 ISciences, LLC
+ * Copyright (C) 2024-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
@@ -14,10 +14,13 @@
#include <sstream>
+#include <geos/algorithm/Orientation.h>
#include <geos/geom/CompoundCurve.h>
#include <geos/geom/CoordinateFilter.h>
+#include <geos/geom/CircularString.h>
#include <geos/geom/GeometryFactory.h>
#include <geos/operation/BoundaryOp.h>
+#include <geos/operation/split/SplitGeometryAtVertex.h>
#include <geos/util.h>
namespace geos {
@@ -315,7 +318,83 @@ CompoundCurve::isEmpty() const
void
CompoundCurve::normalize()
{
- throw util::UnsupportedOperationException();
+ if (isEmpty()) {
+ return;
+ }
+
+ if (isClosed()) {
+ normalizeClosed();
+ return;
+ }
+
+ const CoordinateXY& firstPt = curves.front()->getCoordinatesRO()->front<CoordinateXY>();
+ const CoordinateXY& lastPt = curves.back()->getCoordinatesRO()->back<CoordinateXY>();
+
+ if (firstPt.compareTo(lastPt) > 0) {
+ reverseInPlace();
+ }
+}
+
+
+void
+CompoundCurve::normalizeClosed()
+{
+ std::size_t minCurve = 0;
+ std::size_t minInd = 0;
+ const CoordinateXY* minPt = nullptr;
+
+ auto orientationPoints = getCoordinates();
+
+ // TODO: Avoid copying all coordinates here.
+ // Would require updating Orientation::isCCW to take an iterator
+ if (orientationPoints->size() >= 4 && algorithm::Orientation::isCCW(orientationPoints.get())) {
+ reverseInPlace();
+ }
+
+ for (std::size_t iCurve = 0; iCurve < curves.size(); iCurve++) {
+ const CoordinateSequence* curvePts = curves[iCurve]->getCoordinatesRO();
+
+ auto nPts = curvePts == nullptr ? 0 : curvePts->size();
+
+ std::size_t step = curves[iCurve]->getGeometryTypeId() == GEOS_CIRCULARSTRING ? 2 : 1;
+
+ for (std::size_t i = 0 ; i < nPts; i += step) {
+ const CoordinateXY* pt = &curvePts->getAt<CoordinateXY>(i);
+ if (minPt == nullptr || pt->compareTo(*minPt) < 0) {
+ minPt = pt;
+ minCurve = iCurve;
+ minInd = i;
+ }
+ }
+ }
+
+ if (minCurve == 0 && minInd == 0) {
+ return;
+ }
+
+ std::vector<std::unique_ptr<SimpleCurve>> newCurves;
+ std::unique_ptr<SimpleCurve> finalCurve;
+ if (minInd == 0) {
+ newCurves.push_back(std::move(curves[minCurve]));
+ } else if (minInd == curves[minCurve]->getNumPoints() - 1) {
+ finalCurve = std::move(curves[minCurve]);
+ } else {
+ auto split = operation::split::SplitGeometryAtVertex::splitSimpleCurveAtVertex(*curves[minCurve], minInd);
+ newCurves.push_back(std::move(split.second));
+ finalCurve = std::move(split.first);
+ }
+
+ for (std::size_t i = minCurve + 1; i < curves.size(); i++) {
+ newCurves.push_back(std::move(curves[i]));
+ }
+ for (std::size_t i = 0; i < minCurve; i++) {
+ newCurves.push_back(std::move(curves[i]));
+ }
+ if (finalCurve) {
+ newCurves.push_back(std::move(finalCurve));
+ }
+
+ curves = std::move(newCurves);
}
std::unique_ptr<CompoundCurve>
@@ -335,6 +414,16 @@ CompoundCurve::reverseImpl() const
return getFactory()->createCompoundCurve(std::move(reversed)).release();
}
+void
+CompoundCurve::reverseInPlace()
+{
+ std::reverse(curves.begin(), curves.end());
+ for (auto& curve: curves) {
+ curve = curve->reverse();
+ }
+}
+
+
void
CompoundCurve::validateConstruction() const
{
diff --git a/src/geom/SimpleCurve.cpp b/src/geom/SimpleCurve.cpp
index e882f8fdf..40fbf460b 100644
--- a/src/geom/SimpleCurve.cpp
+++ b/src/geom/SimpleCurve.cpp
@@ -330,5 +330,11 @@ SimpleCurve::isEmpty() const
return points->isEmpty();
}
+std::unique_ptr<SimpleCurve>
+SimpleCurve::reverse() const
+{
+ return std::unique_ptr<SimpleCurve>(static_cast<SimpleCurve*>(reverseImpl()));
+}
+
} // namespace geos::geom
} // namespace geos
diff --git a/src/operation/split/SplitGeometryAtVertex.cpp b/src/operation/split/SplitGeometryAtVertex.cpp
new file mode 100644
index 000000000..eba4beeef
--- /dev/null
+++ b/src/operation/split/SplitGeometryAtVertex.cpp
@@ -0,0 +1,84 @@
+/**********************************************************************
+*
+ * 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/operation/split/SplitGeometryAtVertex.h>
+
+#include <geos/geom/LineString.h>
+#include <geos/geom/CircularString.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/util/UnsupportedOperationException.h>
+
+using geos::geom::CoordinateSequence;
+using geos::geom::CircularString;
+using geos::geom::LineString;
+using geos::geom::SimpleCurve;
+using geos::geom::GeometryTypeId;
+
+namespace geos::operation::split {
+std::pair<std::unique_ptr<LineString>, std::unique_ptr<LineString>>
+ SplitGeometryAtVertex::splitLineStringAtVertex(const LineString& ls, std::size_t i)
+{
+ const auto& gf = *ls.getFactory();
+ const CoordinateSequence& pts = *ls.getCoordinatesRO();
+
+ auto pts1 = std::make_shared<CoordinateSequence>(0, pts.hasZ(), pts.hasM());
+ auto pts2 = std::make_shared<CoordinateSequence>(0, pts.hasZ(), pts.hasM());
+
+ if (i > 0) {
+ pts1->add(pts, 0, i);
+ }
+ if (i < pts.size() - 1) {
+ pts2->add(pts, i, pts.size() - 1);
+ }
+
+ return { gf.createLineString(pts1), gf.createLineString(pts2) };
+}
+
+std::pair<std::unique_ptr<CircularString>, std::unique_ptr<CircularString>>
+ SplitGeometryAtVertex::splitCircularStringAtVertex(const CircularString& cs, std::size_t i)
+{
+ const auto& gf = *cs.getFactory();
+ const CoordinateSequence& pts = *cs.getCoordinatesRO();
+
+ if (i % 2) {
+ throw util::IllegalArgumentException("Cannot split CircularString at arc control point");
+ }
+
+ auto pts1 = std::make_shared<CoordinateSequence>(0, pts.hasZ(), pts.hasM());
+ auto pts2 = std::make_shared<CoordinateSequence>(0, pts.hasZ(), pts.hasM());
+
+ if (i > 0) {
+ pts1->add(pts, 0, i);
+ }
+ if (i < pts.size() - 1) {
+ pts2->add(pts, i, pts.size() - 1);
+ }
+
+ return { gf.createCircularString(pts1), gf.createCircularString(pts2) };
+}
+
+std::pair<std::unique_ptr<SimpleCurve>, std::unique_ptr<SimpleCurve>>
+ SplitGeometryAtVertex::splitSimpleCurveAtVertex(const SimpleCurve& sc, std::size_t i)
+{
+ if (sc.getGeometryTypeId() == GeometryTypeId::GEOS_CIRCULARSTRING) {
+ return splitCircularStringAtVertex(static_cast<const CircularString&>(sc), i);
+ }
+ if (sc.getGeometryTypeId() == GeometryTypeId::GEOS_LINESTRING || sc.getGeometryTypeId() == GeometryTypeId::GEOS_LINEARRING) {
+ return splitLineStringAtVertex(static_cast<const LineString&>(sc), i);
+ }
+
+ throw util::UnsupportedOperationException("Unhandled type in SplitGeometryAtVertex::splitAtVertex");
+}
+
+}
\ No newline at end of file
diff --git a/tests/unit/geom/CircularStringTest.cpp b/tests/unit/geom/CircularStringTest.cpp
index c7522314d..6468696da 100644
--- a/tests/unit/geom/CircularStringTest.cpp
+++ b/tests/unit/geom/CircularStringTest.cpp
@@ -42,7 +42,7 @@ struct test_circularstring_data {
auto expected = wktreader_.read<CircularString>(wkt_expected);
- ensure_equals_exact_xyzm(cs->getCoordinatesRO(), expected->getCoordinatesRO(), 0);
+ ensure_equals_exact_geometry_xyzm(cs.get(), expected.get(), 0);
}
};
diff --git a/tests/unit/geom/CompoundCurveTest.cpp b/tests/unit/geom/CompoundCurveTest.cpp
index 499fd3699..ac33abade 100644
--- a/tests/unit/geom/CompoundCurveTest.cpp
+++ b/tests/unit/geom/CompoundCurveTest.cpp
@@ -44,6 +44,15 @@ struct test_compoundcurve_data {
cc_ = factory_->createCompoundCurve(std::move(curves));
}
+ void checkNormalize(const std::string& wkt_in, const std::string& wkt_expected) const {
+ auto cs = wktreader_.read(wkt_in);
+ cs->normalize();
+
+ auto expected = wktreader_.read(wkt_expected);
+
+ ensure_equals_exact_geometry_xyzm(cs.get(), expected.get(), 0);
+ }
+
};
typedef test_group<test_compoundcurve_data> group;
@@ -128,6 +137,8 @@ template<>
template<>
void object::test<3>()
{
+ set_test_name("operations");
+
// Predicates
ensure_THROW(cc_->contains(cc_.get()), geos::util::UnsupportedOperationException);
ensure_THROW(cc_->coveredBy(cc_.get()), geos::util::UnsupportedOperationException);
@@ -180,7 +191,6 @@ void object::test<3>()
ensure("reverse", cc_->reverse()->equalsIdentical(wktreader_.read(""
"COMPOUNDCURVE ((2 2, 2 0), CIRCULARSTRING (2 0, 1 1, 0 0))").get()));
auto cc3 = cc_->reverse();
- ensure_THROW(cc3->normalize(), geos::util::UnsupportedOperationException);
}
// GeometryFilter
@@ -337,6 +347,8 @@ template<>
template<>
void object::test<9>()
{
+ set_test_name("cannot create non-contiguous CompoundCurve");
+
std::vector<std::unique_ptr<SimpleCurve>> curves;
curves.push_back(wktreader_.read<SimpleCurve>("LINESTRING (0 0, 1 2)"));
@@ -349,4 +361,49 @@ void object::test<9>()
ensure_THROW(factory_->createCompoundCurve(std::move(curves)), geos::util::IllegalArgumentException);
}
+template<>
+template<>
+void object::test<10>()
+{
+ set_test_name("normalize non-closed CompoundCurve");
+
+ // already normalized
+ checkNormalize("COMPOUNDCURVE((-2 2, -3 5, 0 10), CIRCULARSTRING (0 10, -5 5, 0 0))",
+ "COMPOUNDCURVE((-2 2, -3 5, 0 10), CIRCULARSTRING (0 10, -5 5, 0 0))");
+
+ checkNormalize("COMPOUNDCURVE((-2 2, -3 5, 0 10), CIRCULARSTRING (0 10, -5 5, 0 0))",
+ "COMPOUNDCURVE((-2 2, -3 5, 0 10), CIRCULARSTRING (0 10, -5 5, 0 0))");
+
+ // needs to be reversed
+ checkNormalize("COMPOUNDCURVE(CIRCULARSTRING (0 0, -5 5, 0 10), (0 10, -3 5, -2 2))",
+ "COMPOUNDCURVE((-2 2, -3 5, 0 10), CIRCULARSTRING (0 10, -5 5, 0 0))");
+}
+
+template<>
+template<>
+void object::test<11>()
+{
+ set_test_name("normalize closed CompoundCurve");
+ // already normalized
+ checkNormalize("COMPOUNDCURVE(CIRCULARSTRING(-5 0, 0 5, 5 0), (5 0, -5 0))",
+ "COMPOUNDCURVE(CIRCULARSTRING(-5 0, 0 5, 5 0), (5 0, -5 0))");
+
+ // needs to be reversed
+ checkNormalize("COMPOUNDCURVE((-5 0, 5 0), CIRCULARSTRING(5 0, 0 5, -5 0))",
+ "COMPOUNDCURVE(CIRCULARSTRING(-5 0, 0 5, 5 0), (5 0, -5 0))");
+
+ // needs to be reversed and scrolled
+ checkNormalize("COMPOUNDCURVE((5 0, -5 0), CIRCULARSTRING(-5 0, 0 5, 5 0))",
+ "COMPOUNDCURVE(CIRCULARSTRING(-5 0, 0 5, 5 0), (5 0, -5 0))");
+
+ // needs to be reversed and scrolled, but no geometries split
+ // actual minimum point is a control point and cannot become the origin
+ checkNormalize("COMPOUNDCURVE (CIRCULARSTRING (0 10, -5 5, 0 0), (0 0, 0 10))",
+ "COMPOUNDCURVE(CIRCULARSTRING (0 0, -5 5, 0 10), (0 10, 0 0))");
+
+ // needs to be reversed, scrolled, split
+ checkNormalize("COMPOUNDCURVE (CIRCULARSTRING (10 0, 0 -10, -10 0, -5 5, 0 0), (0 0, 10 0))",
+ "COMPOUNDCURVE (CIRCULARSTRING (-10 0, -5 5, 0 0), (0 0, 10 0), CIRCULARSTRING (10 0, 0 -10, -10 0))");
+}
+
}
diff --git a/tests/unit/geom/MultiCurveTest.cpp b/tests/unit/geom/MultiCurveTest.cpp
index 1ca766cf1..365e77e7b 100644
--- a/tests/unit/geom/MultiCurveTest.cpp
+++ b/tests/unit/geom/MultiCurveTest.cpp
@@ -131,6 +131,8 @@ template<>
template<>
void object::test<3>()
{
+ set_test_name("operations");
+
// Predicates
ensure_THROW(mc_->contains(mc_.get()), geos::util::UnsupportedOperationException);
ensure_THROW(mc_->coveredBy(mc_.get()), geos::util::UnsupportedOperationException);
@@ -183,7 +185,6 @@ void object::test<3>()
" (10 11, 8 9),"
" CIRCULARSTRING (1.7 1, 1.6 0.5, 1.6 0.4, 1.4 0.4, 1.7 1))").get()));
auto cc3 = mc_->reverse();
- ensure_THROW(cc3->normalize(), geos::util::UnsupportedOperationException);
}
// isClosed
diff --git a/tests/unit/operation/split/SplitGeometryAtVertexTest.cpp b/tests/unit/operation/split/SplitGeometryAtVertexTest.cpp
new file mode 100644
index 000000000..da1a96449
--- /dev/null
+++ b/tests/unit/operation/split/SplitGeometryAtVertexTest.cpp
@@ -0,0 +1,101 @@
+#include <tut/tut.hpp>
+#include <tut/tut_macros.hpp>
+#include <utility.h>
+
+#include <geos/geom/CircularString.h>
+#include <geos/geom/LineString.h>
+#include <geos/operation/split/SplitGeometryAtVertex.h>
+#include <geos/io/WKTReader.h>
+
+using geos::geom::CircularString;
+using geos::geom::LineString;
+using geos::operation::split::SplitGeometryAtVertex;
+
+namespace tut {
+
+struct test_splitgeometryatvertex_data {
+ const geos::io::WKTReader reader_;
+};
+
+typedef test_group<test_splitgeometryatvertex_data, 255> group;
+typedef group::object object;
+
+group test_splitgeometryatvertextest_group("geos::operation::split::SplitGeometryAtVertex");
+
+template<>
+template<>
+void object::test<1>()
+{
+ set_test_name("LineString");
+
+ auto input = reader_.read<LineString>("LINESTRING ZM (0 3 2 3, 5 8 3 4, 2 2 4 5, 6 1 5 6)");
+
+ {
+ auto splitAtStart = SplitGeometryAtVertex::splitSimpleCurveAtVertex(*input, 0);
+
+ ensure(splitAtStart.first->isEmpty());
+ ensure(splitAtStart.first->hasZ());
+ ensure(splitAtStart.first->hasM());
+ ensure(splitAtStart.second->equalsIdentical(input.get()));
+ }
+
+ {
+ auto splitAtEnd = SplitGeometryAtVertex::splitSimpleCurveAtVertex(*input, input->getNumPoints() - 1);
+
+ ensure(splitAtEnd.first->equalsIdentical(input.get()));
+ ensure(splitAtEnd.second->isEmpty());
+ ensure(splitAtEnd.second->hasZ());
+ ensure(splitAtEnd.second->hasM());
+ }
+
+ {
+ auto splitInMiddle = SplitGeometryAtVertex::splitSimpleCurveAtVertex(*input, 2);
+
+ auto expectedFirst = reader_.read("LINESTRING ZM (0 3 2 3, 5 8 3 4, 2 2 4 5)");
+ auto expectedSecond = reader_.read("LINESTRING ZM (2 2 4 5, 6 1 5 6)");
+
+ ensure(splitInMiddle.first->equalsIdentical(expectedFirst.get()));
+ ensure(splitInMiddle.second->equalsIdentical(expectedSecond.get()));
+ }
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+ set_test_name("CircularString");
+
+ auto input = reader_.read<CircularString>("CIRCULARSTRING ZM (-5 0 1 2, 0 5 2 3, 5 0 3 4, 10 -5 4 5, 15 0 5 6)");
+
+ {
+ auto splitAtStart = SplitGeometryAtVertex::splitSimpleCurveAtVertex(*input, 0);
+
+ ensure(splitAtStart.first->isEmpty());
+ ensure(splitAtStart.first->hasZ());
+ ensure(splitAtStart.first->hasM());
+ ensure(splitAtStart.second->equalsIdentical(input.get()));
+ }
+
+ {
+ auto splitAtEnd = SplitGeometryAtVertex::splitSimpleCurveAtVertex(*input, input->getNumPoints() - 1);
+
+ ensure(splitAtEnd.first->equalsIdentical(input.get()));
+ ensure(splitAtEnd.second->isEmpty());
+ ensure(splitAtEnd.second->hasZ());
+ ensure(splitAtEnd.second->hasM());
+ }
+
+ {
+ auto splitInMiddle = SplitGeometryAtVertex::splitSimpleCurveAtVertex(*input, 2);
+
+ auto expectedFirst = reader_.read("CIRCULARSTRING ZM (-5 0 1 2, 0 5 2 3, 5 0 3 4)");
+ auto expectedSecond = reader_.read("CIRCULARSTRING ZM (5 0 3 4, 10 -5 4 5, 15 0 5 6)");
+
+ ensure(splitInMiddle.first->equalsIdentical(expectedFirst.get()));
+ ensure(splitInMiddle.second->equalsIdentical(expectedSecond.get()));
+ }
+
+ ensure_THROW(SplitGeometryAtVertex::splitSimpleCurveAtVertex(*input, 1), geos::util::IllegalArgumentException);
+}
+
+}
diff --git a/tests/unit/utility.h b/tests/unit/utility.h
index 1e20cf61d..7736d6de6 100644
--- a/tests/unit/utility.h
+++ b/tests/unit/utility.h
@@ -394,9 +394,8 @@ ensure_equals_exact_geometry_xyz(const geos::geom::Geometry *lhs_in,
using geos::geom::CoordinateSequence;
using geos::geom::GeometryCollection;
- ensure_equals("type id do not match",
- lhs_in->getGeometryTypeId(), rhs_in->getGeometryTypeId());
-
+ ensure_equals("types do not match",
+ lhs_in->getGeometryType(), rhs_in->getGeometryType());
if (const Point* gpt1 = dynamic_cast<const Point *>(lhs_in)) {
const Point *gpt2 = static_cast<const Point *>(rhs_in);
@@ -453,8 +452,8 @@ ensure_equals_exact_geometry_xyzm(const geos::geom::Geometry *lhs_in,
using geos::geom::CoordinateSequence;
using geos::geom::GeometryCollection;
- ensure_equals("type id do not match",
- lhs_in->getGeometryTypeId(), rhs_in->getGeometryTypeId());
+ ensure_equals("types do not match",
+ lhs_in->getGeometryType(), rhs_in->getGeometryType());
if (const Point* gpt1 = dynamic_cast<const Point *>(lhs_in)) {
const Point *gpt2 = static_cast<const Point *>(rhs_in);
commit c89ef8e0f371ca82387f821b011c64a02012cba4
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Mar 10 11:00:39 2026 -0400
geos_unit: support CompoundCurve in ensure_geometry_equals_exact_xyzm
diff --git a/tests/unit/utility.h b/tests/unit/utility.h
index ba456c34c..1e20cf61d 100644
--- a/tests/unit/utility.h
+++ b/tests/unit/utility.h
@@ -9,6 +9,7 @@
// geos
#include <geos/geom/Geometry.h>
#include <geos/geom/GeometryCollection.h>
+#include <geos/geom/CompoundCurve.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/Dimension.h>
#include <geos/geom/Point.h>
@@ -446,6 +447,7 @@ ensure_equals_exact_geometry_xyzm(const geos::geom::Geometry *lhs_in,
using geos::geom::Point;
using geos::geom::Curve;
+ using geos::geom::CompoundCurve;
using geos::geom::SimpleCurve;
using geos::geom::Surface;
using geos::geom::CoordinateSequence;
@@ -462,6 +464,17 @@ ensure_equals_exact_geometry_xyzm(const geos::geom::Geometry *lhs_in,
const SimpleCurve *gln2 = static_cast<const SimpleCurve*>(rhs_in);
return ensure_equals_exact_xyzm(gln1->getCoordinatesRO(), gln2->getCoordinatesRO(), tolerance);
}
+ else if (const CompoundCurve* cc1 = dynamic_cast<const CompoundCurve*>(lhs_in)) {
+ const CompoundCurve *cc2 = static_cast<const CompoundCurve*>(rhs_in);
+
+ ensure_equals("number of curves does not match",
+ cc1->getNumCurves(),
+ cc2->getNumCurves());
+
+ for (std::size_t i =0; i < cc1->getNumCurves(); i++) {
+ ensure_equals_exact_geometry_xyzm(cc1->getCurveN(i), cc2->getCurveN(i), tolerance);
+ }
+ }
else if (const Surface* gply1 = dynamic_cast<const Surface*>(lhs_in)) {
const Surface* gply2 = static_cast<const Surface*>(rhs_in);
const Curve* extRing1 = gply1->getExteriorRing();
commit 377631cf28007b1d59649b9cb6dcd8bf23083fcc
Author: Daniel Baston <dbaston at gmail.com>
Date: Tue Mar 10 10:25:37 2026 -0400
CircularString: Support normalize()
diff --git a/include/geos/geom/CircularArc.h b/include/geos/geom/CircularArc.h
index 184b07f46..067e5ba39 100644
--- a/include/geos/geom/CircularArc.h
+++ b/include/geos/geom/CircularArc.h
@@ -101,6 +101,8 @@ public:
return m_pos;
}
+ Envelope getEnvelope() const;
+
/// Return the length of the arc
double getLength() const;
diff --git a/include/geos/geom/CircularString.h b/include/geos/geom/CircularString.h
index e38a5b640..54089f3bc 100644
--- a/include/geos/geom/CircularString.h
+++ b/include/geos/geom/CircularString.h
@@ -48,6 +48,8 @@ public:
return true;
}
+ void normalize() override;
+
std::unique_ptr<CircularString> reverse() const
{
return std::unique_ptr<CircularString>(reverseImpl());
@@ -87,6 +89,8 @@ protected:
private:
void createArcs() const;
+ void normalizeClosed();
+
mutable std::vector<CircularArc> arcs;
};
diff --git a/include/geos/geom/CoordinateSequence.h b/include/geos/geom/CoordinateSequence.h
index c284b9bc2..b19b0f5ec 100644
--- a/include/geos/geom/CoordinateSequence.h
+++ b/include/geos/geom/CoordinateSequence.h
@@ -624,9 +624,14 @@ public:
/// Get a string representation of CoordinateSequence
std::string toString() const;
- /// Returns lower-left Coordinate in list
+ /// Returns lower-left Coordinate in list, or nullptr if the
+ /// sequence is empty
const CoordinateXY* minCoordinate() const;
+ /// Returns the index of the minCoordinate, or the maximum value of
+ /// size_t if the sequence is empty
+ std::size_t minCoordinateIndex() const;
+
/** \brief
* Returns either the given CoordinateSequence if its length
* is greater than the given amount, or an empty CoordinateSequence.
@@ -658,6 +663,8 @@ public:
/// Scroll given CoordinateSequence so to start with given Coordinate.
static void scroll(CoordinateSequence* cl, const CoordinateXY* firstCoordinate);
+ void scroll(const CoordinateXY* firstCoordinate);
+ void scroll(std::size_t firstCoordinateIndex);
/** \brief
* Determines which orientation of the {@link Coordinate} array
diff --git a/include/geos/geom/LineString.h b/include/geos/geom/LineString.h
index 8d59cda5a..25764c73d 100644
--- a/include/geos/geom/LineString.h
+++ b/include/geos/geom/LineString.h
@@ -96,6 +96,14 @@ public:
return false;
}
+ /** \brief
+ * Normalizes a LineString.
+ *
+ * A normalized LineString has the first point which is not equal to
+ * its reflected point less than the reflected point.
+ */
+ void normalize() override;
+
/**
* Creates a LineString whose coordinates are in the reverse
* order of this object's
@@ -134,6 +142,8 @@ protected:
private:
+ void normalizeClosed();
+
void validateConstruction();
};
diff --git a/include/geos/geom/Polygon.h b/include/geos/geom/Polygon.h
index aabc4d866..98e4fe262 100644
--- a/include/geos/geom/Polygon.h
+++ b/include/geos/geom/Polygon.h
@@ -129,7 +129,7 @@ protected:
private:
- void normalize(LinearRing* ring, bool clockwise);
+ static void normalize(LinearRing* ring, bool clockwise);
};
diff --git a/include/geos/geom/SimpleCurve.h b/include/geos/geom/SimpleCurve.h
index 9d0538058..2c7e4c8f4 100644
--- a/include/geos/geom/SimpleCurve.h
+++ b/include/geos/geom/SimpleCurve.h
@@ -101,15 +101,6 @@ public:
bool isEmpty() const override;
- /** \brief
- * Normalizes a SimpleCurve.
- *
- * A normalized simple curve
- * has the first point which is not equal to its reflected point
- * less than the reflected point.
- */
- void normalize() override;
-
protected:
SimpleCurve(const SimpleCurve& other);
@@ -128,11 +119,6 @@ protected:
std::shared_ptr<const CoordinateSequence> points;
mutable Envelope envelope;
-
-
-private:
-
- void normalizeClosed();
};
}
diff --git a/src/geom/CircularArc.cpp b/src/geom/CircularArc.cpp
index 9b5476953..1ec27bbe6 100644
--- a/src/geom/CircularArc.cpp
+++ b/src/geom/CircularArc.cpp
@@ -276,6 +276,13 @@ CircularArc::getArea() const {
return R*R/2*(theta - std::sin(theta));
}
+Envelope
+CircularArc::getEnvelope() const {
+ Envelope env;
+ algorithm::CircularArcs::expandEnvelope(env, p0(), p1(), p2());
+ return env;
+}
+
double
CircularArc::getLength() const {
if (isLinear()) {
diff --git a/src/geom/CircularString.cpp b/src/geom/CircularString.cpp
index ebfdd765b..fe6b79cd5 100644
--- a/src/geom/CircularString.cpp
+++ b/src/geom/CircularString.cpp
@@ -94,6 +94,66 @@ CircularString::getLength() const
return tot;
}
+void
+CircularString::normalize()
+{
+ if (isEmpty()) return;
+
+ assert(points.get());
+ if (isClosed()) {
+ normalizeClosed();
+ return;
+ }
+
+ if (points->front<CoordinateXY>().compareTo( points->back<CoordinateXY>()) == 1) {
+ if (points.use_count() > 1) {
+ points = points->clone();
+ }
+ const_cast<CoordinateSequence*>(points.get())->reverse();
+ arcs.clear();
+ }
+}
+
+/*private*/
+void
+CircularString::normalizeClosed()
+{
+ if (isEmpty()) {
+ return;
+ }
+
+ const auto& ringCoords = getCoordinatesRO();
+ const bool reverse = ringCoords->size() >= 4 && algorithm::Orientation::isCCW(ringCoords);
+
+ std::size_t minInd = 0;
+ const CoordinateXY* minPt = &ringCoords->getAt<CoordinateXY>(minInd);
+ for (std::size_t i = 2; i < ringCoords->size() - 2; i++) {
+ const CoordinateXY& pt = ringCoords->getAt<CoordinateXY>(i);
+ if (pt.compareTo(*minPt) < 0) {
+ minInd = i;
+ minPt = &pt;
+ }
+ }
+
+ if (minInd > 0) {
+ auto coords = std::make_shared<CoordinateSequence>(0u, ringCoords->hasZ(), ringCoords->hasM());
+ coords->reserve(ringCoords->size());
+
+ coords->add(*ringCoords, minInd, ringCoords->size() - 2);
+ coords->add(*ringCoords, 0, minInd);
+
+ if (reverse) {
+ coords->reverse();
+ }
+ points = std::move(coords);
+ } else if (reverse) {
+ if (points.use_count() > 1) {
+ points = points->clone();
+ }
+ const_cast<CoordinateSequence*>(points.get())->reverse();
+ }
+}
+
CircularString*
CircularString::reverseImpl() const
{
diff --git a/src/geom/CoordinateSequence.cpp b/src/geom/CoordinateSequence.cpp
index 865f97a44..cd97014e3 100644
--- a/src/geom/CoordinateSequence.cpp
+++ b/src/geom/CoordinateSequence.cpp
@@ -361,15 +361,26 @@ CoordinateSequence::atLeastNCoordinatesOrNothing(std::size_t n,
const CoordinateXY*
CoordinateSequence::minCoordinate() const
+{
+ if (isEmpty()) {
+ return nullptr;
+ }
+ return &getAt<CoordinateXY>(minCoordinateIndex());
+}
+
+std::size_t
+CoordinateSequence::minCoordinateIndex() const
{
const CoordinateXY* minCoord = nullptr;
+ std::size_t minIndex = std::numeric_limits<std::size_t>::max();
const std::size_t p_size = getSize();
for(std::size_t i = 0; i < p_size; i++) {
if(minCoord == nullptr || minCoord->compareTo(getAt<CoordinateXY>(i)) > 0) {
minCoord = &getAt<CoordinateXY>(i);
+ minIndex = i;
}
}
- return minCoord;
+ return minIndex;
}
size_t
@@ -386,17 +397,28 @@ CoordinateSequence::indexOf(const CoordinateXY* coordinate,
}
void
-CoordinateSequence::scroll(CoordinateSequence* cl,
- const CoordinateXY* firstCoordinate)
+CoordinateSequence::scroll(CoordinateSequence* cl, const CoordinateXY* firstCoordinate)
{
- std::size_t ind = indexOf(firstCoordinate, cl);
+ cl->scroll(firstCoordinate);
+}
+
+void
+CoordinateSequence::scroll(const CoordinateXY* firstCoordinate)
+{
+ std::size_t ind = indexOf(firstCoordinate, this);
if(ind == 0 || ind == std::numeric_limits<std::size_t>::max()) {
return; // not found or already first
}
- std::rotate(cl->m_vect.begin(),
- std::next(cl->m_vect.begin(), static_cast<std::ptrdiff_t>(ind * cl->stride())),
- cl->m_vect.end());
+ scroll(ind);
+}
+
+void
+CoordinateSequence::scroll(size_t ind)
+{
+ std::rotate(m_vect.begin(),
+ std::next(m_vect.begin(), static_cast<std::ptrdiff_t>(ind * stride())),
+ m_vect.end());
}
int
diff --git a/src/geom/LineString.cpp b/src/geom/LineString.cpp
index fb52a2c5e..b3f1ceb26 100644
--- a/src/geom/LineString.cpp
+++ b/src/geom/LineString.cpp
@@ -122,5 +122,63 @@ LineString::getGeometryTypeId() const
return GEOS_LINESTRING;
}
+void
+LineString::normalize()
+{
+ if (isEmpty()) return;
+ assert(points.get());
+ if (isClosed()) {
+ normalizeClosed();
+ return;
+ }
+ std::size_t npts = points->getSize();
+ std::size_t n = npts / 2;
+ for (std::size_t i = 0; i < n; i++) {
+ std::size_t j = npts - 1 - i;
+ if (!(points->getAt<CoordinateXY>(i) == points->getAt<CoordinateXY>(j))) {
+ if (points->getAt<CoordinateXY>(i).compareTo(points->getAt<CoordinateXY>(j)) > 0) {
+ if (points.use_count() > 1) {
+ points = points->clone();
+ }
+ const_cast<CoordinateSequence*>(points.get())->reverse();
+ }
+ return;
+ }
+ }
+}
+
+/*private*/
+void
+LineString::normalizeClosed()
+{
+ if (isEmpty()) {
+ return;
+ }
+
+ const auto& ringCoords = getCoordinatesRO();
+ std::size_t minIndex = ringCoords->minCoordinateIndex();
+
+ const bool isCCW = ringCoords->size() >= 4 && Orientation::isCCW(ringCoords);
+
+ if (minIndex == 0 && !isCCW) {
+ // already normalized
+ return;
+ }
+
+ auto coords = std::make_shared<CoordinateSequence>(0u, ringCoords->hasZ(), ringCoords->hasM());
+ coords->reserve(ringCoords->size());
+ // exclude last point (repeated)
+ coords->add(*ringCoords, 0, ringCoords->size() - 2);
+ coords->scroll(minIndex);
+ coords->closeRing(true);
+
+ if (isCCW) {
+ coords->reverse();
+ }
+
+ points = coords;
+}
+
+
} // namespace geos::geom
} // namespace geos
diff --git a/src/geom/Polygon.cpp b/src/geom/Polygon.cpp
index 850331c04..116374ca9 100644
--- a/src/geom/Polygon.cpp
+++ b/src/geom/Polygon.cpp
@@ -118,18 +118,25 @@ Polygon::normalize(LinearRing* ring, bool clockwise)
}
const auto& ringCoords = ring->getCoordinatesRO();
+ std::size_t minCoordinateIndex = ringCoords->minCoordinateIndex();
+
+ const bool switchOrientation = algorithm::Orientation::isCCW(ringCoords) == clockwise;
+
+ if (minCoordinateIndex == 0 && !switchOrientation) {
+ // already normalized
+ return;
+ }
+
CoordinateSequence coords(0u, ringCoords->hasZ(), ringCoords->hasM());
coords.reserve(ringCoords->size());
// exclude last point (repeated)
coords.add(*ringCoords, 0, ringCoords->size() - 2);
- const CoordinateXY* minCoordinate = coords.minCoordinate();
-
- CoordinateSequence::scroll(&coords, minCoordinate);
+ coords.scroll(minCoordinateIndex);
coords.closeRing();
- if(algorithm::Orientation::isCCW(&coords) == clockwise) {
+ if(switchOrientation) {
coords.reverse();
}
ring->setPoints(&coords);
diff --git a/src/geom/SimpleCurve.cpp b/src/geom/SimpleCurve.cpp
index 21fd40c88..e882f8fdf 100644
--- a/src/geom/SimpleCurve.cpp
+++ b/src/geom/SimpleCurve.cpp
@@ -330,60 +330,5 @@ SimpleCurve::isEmpty() const
return points->isEmpty();
}
-/*public*/
-void
-SimpleCurve::normalize()
-{
- util::ensureNoCurvedComponents(*this);
-
- if (isEmpty()) return;
- assert(points.get());
- if (isClosed()) {
- normalizeClosed();
- return;
- }
- std::size_t npts = points->getSize();
- std::size_t n = npts / 2;
- for (std::size_t i = 0; i < n; i++) {
- std::size_t j = npts - 1 - i;
- if (!(points->getAt<CoordinateXY>(i) == points->getAt<CoordinateXY>(j))) {
- if (points->getAt<CoordinateXY>(i).compareTo(points->getAt<CoordinateXY>(j)) > 0) {
- if (points.use_count() > 1) {
- points = points->clone();
- }
- const_cast<CoordinateSequence*>(points.get())->reverse();
- }
- return;
- }
- }
-}
-
-/*private*/
-void
-SimpleCurve::normalizeClosed()
-{
- if (isEmpty()) {
- return;
- }
-
- const auto& ringCoords = getCoordinatesRO();
-
- auto coords = detail::make_unique<CoordinateSequence>(0u, ringCoords->hasZ(), ringCoords->hasM());
- coords->reserve(ringCoords->size());
- // exclude last point (repeated)
- coords->add(*ringCoords, 0, ringCoords->size() - 2);
-
- const CoordinateXY* minCoordinate = coords->minCoordinate();
-
- CoordinateSequence::scroll(coords.get(), minCoordinate);
- coords->closeRing(true);
-
- if (coords->size() >= 4 && algorithm::Orientation::isCCW(coords.get())) {
- coords->reverse();
- }
-
- points = std::move(coords);
-}
-
} // namespace geos::geom
} // namespace geos
diff --git a/tests/unit/geom/CircularStringTest.cpp b/tests/unit/geom/CircularStringTest.cpp
index 8204468c8..c7522314d 100644
--- a/tests/unit/geom/CircularStringTest.cpp
+++ b/tests/unit/geom/CircularStringTest.cpp
@@ -35,6 +35,15 @@ struct test_circularstring_data {
cs_ = factory_->createCircularString(seq);
}
+
+ void checkNormalize(const std::string& wkt_in, const std::string& wkt_expected) const {
+ auto cs = wktreader_.read<CircularString>(wkt_in);
+ cs->normalize();
+
+ auto expected = wktreader_.read<CircularString>(wkt_expected);
+
+ ensure_equals_exact_xyzm(cs->getCoordinatesRO(), expected->getCoordinatesRO(), 0);
+ }
};
typedef test_group<test_circularstring_data> group;
@@ -158,7 +167,6 @@ void object::test<3>()
ensure("reverse", cs_->reverse()->equalsIdentical(wktreader_.read("CIRCULARSTRING (4 0, 3 -1, 2 0, 1 1, 0 0)").get()));
auto cs3 = cs_->reverse();
- ensure_THROW(cs3->normalize(), geos::util::UnsupportedOperationException);
}
// SimpleCurve API
@@ -212,4 +220,70 @@ void object::test<6>()
ensure(arcs.empty());
}
+template<>
+template<>
+void object::test<7>()
+{
+ set_test_name("normalize of closed CircularString");
+
+ // circle, already normalized
+ checkNormalize("CIRCULARSTRING (-5 0, 0 5, 5 0, 0 -5, -5 0)",
+ "CIRCULARSTRING (-5 0, 0 5, 5 0, 0 -5, -5 0)");
+
+ // circle, start point is OK but need to reverse points
+ checkNormalize("CIRCULARSTRING (-5 0, 0 -5, 5 0, 0 5, -5 0)",
+ "CIRCULARSTRING (-5 0, 0 5, 5 0, 0 -5, -5 0)");
+
+ // circle, need to change start point but orientation is OK
+ checkNormalize("CIRCULARSTRING (5 0, 0 -5, -5 0, 0 5, 5 0)",
+ "CIRCULARSTRING (-5 0, 0 5, 5 0, 0 -5, -5 0)");
+
+ // circle, need to change start point and orientation
+ checkNormalize("CIRCULARSTRING (5 0, 0 5, -5 0, 0 -5, 5 0)",
+ "CIRCULARSTRING (-5 0, 0 5, 5 0, 0 -5, -5 0)");
+
+ // half-moon, already normalized
+ checkNormalize("CIRCULARSTRING (0 0, -5 5, 0 10, -3 5, 0 0)",
+ "CIRCULARSTRING (0 0, -5 5, 0 10, -3 5, 0 0)");
+
+ // half-moon, already normalized
+ checkNormalize("CIRCULARSTRING (0 0, -5 5, 0 10, -3 5, 0 0)",
+ "CIRCULARSTRING (0 0, -5 5, 0 10, -3 5, 0 0)");
+
+ // half-moon, start point is OK but need to make CW
+ checkNormalize("CIRCULARSTRING (0 0, -3 5, 0 10, -5 5, 0 0)",
+ "CIRCULARSTRING (0 0, -5 5, 0 10, -3 5, 0 0)");
+
+ // half-moon, need to change start point but orientation is OK
+ checkNormalize("CIRCULARSTRING (0 10, -3 5, 0 0, -5 5, 0 10)",
+ "CIRCULARSTRING (0 0, -5 5, 0 10, -3 5, 0 0)");
+
+ // half-moon, need to change start point and orientation
+ checkNormalize("CIRCULARSTRING (0 10, -5 5, 0 0, -3 5, 0 10)",
+ "CIRCULARSTRING (0 0, -5 5, 0 10, -3 5, 0 0)");
+}
+
+template<>
+template<>
+void object::test<8>()
+{
+ set_test_name("normalize open CircularString");
+
+ // already normalized
+ checkNormalize("CIRCULARSTRING (-10 0, 0 10, 10 0, 0 5, -5 0)",
+ "CIRCULARSTRING (-10 0, 0 10, 10 0, 0 5, -5 0)");
+
+ // need to reverse
+ checkNormalize("CIRCULARSTRING (-5 0, 0 5, 10 0, 0 10, -10 0)",
+ "CIRCULARSTRING (-10 0, 0 10, 10 0, 0 5, -5 0)");
+
+ // already normalized
+ checkNormalize("CIRCULARSTRING (-2 2, -3 5, 0 10, -5 5, 0 0)",
+ "CIRCULARSTRING (-2 2, -3 5, 0 10, -5 5, 0 0)");
+
+ // need to reverse
+ checkNormalize("CIRCULARSTRING (0 0, -5 5, 0 10, -3 5, -2 2)",
+ "CIRCULARSTRING (-2 2, -3 5, 0 10, -5 5, 0 0)");
+}
+
}
-----------------------------------------------------------------------
Summary of changes:
include/geos/geom/CircularArc.h | 2 +
include/geos/geom/CircularString.h | 6 +-
include/geos/geom/CompoundCurve.h | 4 +
include/geos/geom/CoordinateSequence.h | 9 +-
include/geos/geom/Curve.h | 2 +
include/geos/geom/LineString.h | 10 ++
include/geos/geom/Polygon.h | 2 +-
include/geos/geom/SimpleCurve.h | 14 +--
.../geos/operation/split/SplitGeometryAtVertex.h | 43 +++++++++
src/geom/CircularArc.cpp | 7 ++
src/geom/CircularString.cpp | 60 ++++++++++++
src/geom/CompoundCurve.cpp | 93 ++++++++++++++++++-
src/geom/CoordinateSequence.cpp | 36 ++++++--
src/geom/Curve.cpp | 6 ++
src/geom/CurvePolygon.cpp | 12 ++-
src/geom/LineString.cpp | 51 +++++++++++
src/geom/Polygon.cpp | 15 ++-
src/geom/SimpleCurve.cpp | 55 +----------
src/operation/split/SplitGeometryAtVertex.cpp | 84 +++++++++++++++++
tests/unit/geom/CircularStringTest.cpp | 76 +++++++++++++++-
tests/unit/geom/CompoundCurveTest.cpp | 59 +++++++++++-
tests/unit/geom/CurvePolygonTest.cpp | 32 ++++++-
tests/unit/geom/MultiCurveTest.cpp | 3 +-
tests/unit/geom/MultiSurfaceTest.cpp | 3 +-
.../operation/split/SplitGeometryAtVertexTest.cpp | 101 +++++++++++++++++++++
tests/unit/utility.h | 22 ++++-
26 files changed, 713 insertions(+), 94 deletions(-)
create mode 100644 include/geos/operation/split/SplitGeometryAtVertex.h
create mode 100644 src/operation/split/SplitGeometryAtVertex.cpp
create mode 100644 tests/unit/operation/split/SplitGeometryAtVertexTest.cpp
hooks/post-receive
--
GEOS
More information about the geos-commits
mailing list