[geos-commits] [SCM] GEOS branch main updated. 4b9a5ad063a7ea31662d6cd054a123b8b32099a6

git at osgeo.org git at osgeo.org
Tue May 6 09:48:28 PDT 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  4b9a5ad063a7ea31662d6cd054a123b8b32099a6 (commit)
      from  6f4a7396481ab28dd240cc7b1511949f1892ac2a (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 4b9a5ad063a7ea31662d6cd054a123b8b32099a6
Author: Roman Karavia <3082173+rkaravia at users.noreply.github.com>
Date:   Tue May 6 18:47:59 2025 +0200

    Allow GeoJSONWriter to force polygons to be counterclockwise (#1262)
    
    Add a `setForceCCW` function to GeoJSONWriter, which enables conversion
    of polygons to follow the RFC 7946 winding order.
    When `isForceCCW` is true, linear rings in Polygons and MultiPolygons
    follow the right-hand rule, meaning exterior rings are counterclockwise,
    and holes are clockwise.
    See https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6
    
    This addition to GEOS is inspired by a corresponding change in JTS,
    see https://github.com/locationtech/jts/pull/694

diff --git a/include/geos/io/GeoJSONWriter.h b/include/geos/io/GeoJSONWriter.h
index 002a6f501..d8011b872 100644
--- a/include/geos/io/GeoJSONWriter.h
+++ b/include/geos/io/GeoJSONWriter.h
@@ -95,13 +95,27 @@ public:
      */
     void setOutputDimension(uint8_t newOutputDimension);
 
+    /*
+     * Sets whether the GeoJSON should be output following counter-clockwise orientation aka Right Hand Rule defined in
+     * RFC7946. See <a href="https://tools.ietf.org/html/rfc7946#section-3.1.6">RFC 7946 Specification</a>
+     * for more context.
+     *
+     * @param newIsForceCCW true if the GeoJSON should be output following the RFC7946
+     *                      counter-clockwise orientation aka Right Hand Rule
+     */
+    void setForceCCW(bool newIsForceCCW);
+
 private:
     uint8_t defaultOutputDimension = 3;
 
+    bool isForceCCW = false;
+
     std::vector<double> convertCoordinate(const geom::Coordinate* c);
 
     std::vector<std::vector<double>> convertCoordinateSequence(const geom::CoordinateSequence* c);
 
+    std::vector<std::vector<std::vector<double>>> convertLinearRings(const geom::Polygon *poly);
+
     void encode(const geom::Geometry* g, GeoJSONType type, geos_nlohmann::ordered_json& j);
 
     void encodeGeometry(const geom::Geometry* g, geos_nlohmann::ordered_json& j);
diff --git a/src/io/GeoJSONWriter.cpp b/src/io/GeoJSONWriter.cpp
index 8c9db9d7b..b6981600c 100644
--- a/src/io/GeoJSONWriter.cpp
+++ b/src/io/GeoJSONWriter.cpp
@@ -52,6 +52,10 @@ GeoJSONWriter::setOutputDimension(uint8_t dims)
     defaultOutputDimension = dims;
 }
 
+void GeoJSONWriter::setForceCCW(bool newIsForceCCW) {
+    isForceCCW = newIsForceCCW;
+}
+
 std::string GeoJSONWriter::write(const geom::Geometry* geometry, GeoJSONType type)
 {
     json j;
@@ -223,7 +227,13 @@ void GeoJSONWriter::encodeGeometry(const geom::Geometry* geometry, geos_nlohmann
     }
     else if (type == GEOS_POLYGON) {
         auto poly = static_cast<const geom::Polygon*>(geometry);
-        encodePolygon(poly, j);
+        if (isForceCCW) {
+            auto ccwPoly = poly->clone();
+            ccwPoly->orientRings(false);
+            encodePolygon(ccwPoly.get(), j);
+        } else {
+            encodePolygon(poly, j);
+        }
     }
     else if (type == GEOS_MULTIPOINT) {
         auto multiPoint = static_cast<const geom::MultiPoint*>(geometry);
@@ -264,14 +274,7 @@ void GeoJSONWriter::encodeLineString(const geom::LineString* line, geos_nlohmann
 void GeoJSONWriter::encodePolygon(const geom::Polygon* poly, geos_nlohmann::ordered_json& j)
 {
     j["type"] = "Polygon";
-    std::vector<std::vector<std::vector<double>>> rings;
-    auto ring = poly->getExteriorRing();
-    rings.reserve(poly->getNumInteriorRing()+1);
-    rings.push_back(convertCoordinateSequence(ring->getCoordinates().get()));
-    for (size_t i = 0; i < poly->getNumInteriorRing(); i++) {
-        rings.push_back(convertCoordinateSequence(poly->getInteriorRingN(i)->getCoordinates().get()));
-    }
-    j["coordinates"] = rings;
+    j["coordinates"] = convertLinearRings(poly);
 }
 
 void GeoJSONWriter::encodeMultiPoint(const geom::MultiPoint* multiPoint, geos_nlohmann::ordered_json& j)
@@ -298,14 +301,13 @@ void GeoJSONWriter::encodeMultiPolygon(const geom::MultiPolygon* multiPolygon, g
     polygons.reserve(multiPolygon->getNumGeometries());
     for (size_t i = 0; i < multiPolygon->getNumGeometries(); i++) {
         const Polygon* polygon = multiPolygon->getGeometryN(i);
-        std::vector<std::vector<std::vector<double>>> rings;
-        auto ring = polygon->getExteriorRing();
-        rings.reserve(polygon->getNumInteriorRing() + 1);
-        rings.push_back(convertCoordinateSequence(ring->getCoordinates().get()));
-        for (size_t j = 0; j < polygon->getNumInteriorRing(); j++) {
-            rings.push_back(convertCoordinateSequence(polygon->getInteriorRingN(j)->getCoordinates().get()));
+        if (isForceCCW) {
+            auto ccwPolygon = polygon->clone();
+            ccwPolygon->orientRings(false);
+            polygons.push_back(convertLinearRings(ccwPolygon.get()));
+        } else {
+            polygons.push_back(convertLinearRings(polygon));
         }
-        polygons.push_back(rings);
     }
     json["coordinates"] = polygons;
 }
@@ -342,5 +344,17 @@ std::vector<std::vector<double>> GeoJSONWriter::convertCoordinateSequence(const
     return coordinates;
 }
 
+std::vector<std::vector<std::vector<double>>> GeoJSONWriter::convertLinearRings(const geom::Polygon* poly)
+{
+    std::vector<std::vector<std::vector<double>>> rings;
+    auto ring = poly->getExteriorRing();
+    rings.reserve(poly->getNumInteriorRing() + 1);
+    rings.push_back(convertCoordinateSequence(ring->getCoordinates().get()));
+    for (size_t i = 0; i < poly->getNumInteriorRing(); i++) {
+        rings.push_back(convertCoordinateSequence(poly->getInteriorRingN(i)->getCoordinates().get()));
+    }
+    return rings;
+}
+
 } // namespace geos.io
 } // namespace geos
diff --git a/tests/unit/io/GeoJSONWriterTest.cpp b/tests/unit/io/GeoJSONWriterTest.cpp
index e075e577f..4256e6bab 100644
--- a/tests/unit/io/GeoJSONWriterTest.cpp
+++ b/tests/unit/io/GeoJSONWriterTest.cpp
@@ -453,4 +453,40 @@ void object::test<30>
     ensure_equals(result, "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]},\"properties\":{\"array\":[{\"key\":\"value_1\"},{\"key\":\"value_2\"}]}}");
 }
 
+// Test Polygon right-hand rule
+template<>
+template<>
+void object::test<31>
+()
+{
+    geojsonwriter.setForceCCW(true);
+    GeomPtr geom(wktreader.read("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"));
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"Polygon\",\"coordinates\":[[[0.0,0.0],[1.0,0.0],[1.0,1.0],[0.0,1.0],[0.0,0.0]]]}");
+}
+
+// Test Polygon with hole right-hand rule
+template<>
+template<>
+void object::test<32>
+()
+{
+    geojsonwriter.setForceCCW(true);
+    GeomPtr geom(wktreader.read("POLYGON ((0 0, 0 20, 20 20, 20 0, 0 0), (1 1, 10 1, 10 10, 1 10, 1 1) )"));
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"Polygon\",\"coordinates\":[[[0.0,0.0],[20.0,0.0],[20.0,20.0],[0.0,20.0],[0.0,0.0]],[[1.0,1.0],[1.0,10.0],[10.0,10.0],[10.0,1.0],[1.0,1.0]]]}");
+}
+
+// Test MultiPolygon right-hand rule
+template<>
+template<>
+void object::test<33>
+()
+{
+    geojsonwriter.setForceCCW(true);
+    GeomPtr geom(wktreader.read("MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2 2)))"));
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0.0,0.0],[1.0,0.0],[1.0,1.0],[0.0,1.0],[0.0,0.0]]],[[[2.0,2.0],[3.0,2.0],[3.0,3.0],[2.0,3.0],[2.0,2.0]]]]}");
+}
+
 }

-----------------------------------------------------------------------

Summary of changes:
 include/geos/io/GeoJSONWriter.h     | 14 +++++++++++
 src/io/GeoJSONWriter.cpp            | 46 ++++++++++++++++++++++++-------------
 tests/unit/io/GeoJSONWriterTest.cpp | 36 +++++++++++++++++++++++++++++
 3 files changed, 80 insertions(+), 16 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list