[geos-commits] [SCM] GEOS branch master updated. da2fb44ee0680811c971689ae0f60fe9187b42ae

git at osgeo.org git at osgeo.org
Fri Aug 28 13:33:49 PDT 2020


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, master has been updated
       via  da2fb44ee0680811c971689ae0f60fe9187b42ae (commit)
       via  acbe90de979f66994545ea3b9e18c9e8a8352776 (commit)
      from  dd5b572f414f26ef2c35cba9440ad25208952ed9 (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 da2fb44ee0680811c971689ae0f60fe9187b42ae
Merge: acbe90d dd5b572
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Fri Aug 28 13:33:42 2020 -0700

    Merge branch 'master' of https://git.osgeo.org/gitea/geos/geos


commit acbe90de979f66994545ea3b9e18c9e8a8352776
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Fri Aug 28 13:33:32 2020 -0700

    Port JTS https://github.com/locationtech/jts/commit/be18a8a797763116346ca605f8d7aedc66153773 Improve Orientation isCCW to handle topology collapse

diff --git a/include/geos/algorithm/Orientation.h b/include/geos/algorithm/Orientation.h
index 168b0e4..0ed4bba 100644
--- a/include/geos/algorithm/Orientation.h
+++ b/include/geos/algorithm/Orientation.h
@@ -68,22 +68,25 @@ public:
     static int index(const geom::Coordinate& p1, const geom::Coordinate& p2,
                      const geom::Coordinate& q);
 
-    /** \brief
-     * Computes whether a ring defined by an array of
-     * [Coordinates](@ref geom::Coordinate) is oriented counter-clockwise.
-     *
-     * - The list of points is assumed to have the first and last points equal.
-     * - This will handle coordinate lists which contain repeated points.
-     *
-     * This algorithm is *only* guaranteed to work with valid rings. If the
-     * ring is invalid (e.g. self-crosses or touches), the computed result may not
-     * be correct.
-     *
-     * @param ring an array of Coordinates forming a ring
-     * @return `true` if the ring is oriented counter-clockwise.
-     * @throws IllegalArgumentException
-     *           if there are too few points to determine orientation (< 4)
-     */
+    /**
+    * Computes whether a ring defined by a geom::CoordinateSequence is
+    * oriented counter-clockwise.
+    *
+    *  * The list of points is assumed to have the first and last points equal.
+    *  * This handles coordinate lists which contain repeated points.
+    *  * This handles rings which contain collapsed segments
+    *    (in particular, along the top of the ring).
+    *
+    * This algorithm is guaranteed to work with valid rings.
+    * It also works with "mildly invalid" rings
+    * which contain collapsed (coincident) flat segments along the top of the ring.
+    * If the ring is "more" invalid (e.g. self-crosses or touches),
+    * the computed result may not be correct.
+    *
+    * @param ring a CoordinateSequence forming a ring (with first and last point identical)
+    * @return true if the ring is oriented counter-clockwise.
+    * @throws IllegalArgumentException if there are too few points to determine orientation (< 4)
+    */
     static bool isCCW(const geom::CoordinateSequence* ring);
 
 };
diff --git a/include/geos/algorithm/locate/IndexedPointInAreaLocator.h b/include/geos/algorithm/locate/IndexedPointInAreaLocator.h
index a29fb94..364ef34 100644
--- a/include/geos/algorithm/locate/IndexedPointInAreaLocator.h
+++ b/include/geos/algorithm/locate/IndexedPointInAreaLocator.h
@@ -45,7 +45,7 @@ namespace locate { // geos::algorithm::locate
  * an areal geometry, using indexing for efficiency.
  *
  * The Location is computed precisely, in that points located on the geometry boundary
- * or segments will return [Location::BOUNDARY](@ref geom::Location::BOUNDARY).
+ * or segments will return [geom::Location::BOUNDARY](@ref geom::Location).
  *
  * Polygonal and [LinearRing](@ref geom::LinearRing) geometries are supported.
  *
diff --git a/include/geos/algorithm/locate/SimplePointInAreaLocator.h b/include/geos/algorithm/locate/SimplePointInAreaLocator.h
index 8f0bd04..4c1a678 100644
--- a/include/geos/algorithm/locate/SimplePointInAreaLocator.h
+++ b/include/geos/algorithm/locate/SimplePointInAreaLocator.h
@@ -56,14 +56,14 @@ public:
      *
      * The return value is one of:
      *
-     * - [Location::INTERIOR](@ref geom::Location::INTERIOR)
+     * - geom::Location::INTERIOR
      *   if the point is in the geometry interior
-     * - [Location::BOUNDARY](@ref geom::Location::BOUNDARY)
+     * - geom::Location::BOUNDARY
      *   if the point lies exactly on the boundary
-     * - [Location::EXTERIOR](@ref geom::Location::EXTERIOR)
+     * - geom::Location::EXTERIOR
      *   if the point is outside the geometry
      *
-     * Computes `Location::BOUNDARY` if the point lies exactly
+     * Computes `geom::Location::BOUNDARY` if the point lies exactly
      * on the polygon boundary.
      *
      * @param p the point to test
diff --git a/src/algorithm/Orientation.cpp b/src/algorithm/Orientation.cpp
index 009b97c..64d3c2a 100644
--- a/src/algorithm/Orientation.cpp
+++ b/src/algorithm/Orientation.cpp
@@ -23,6 +23,7 @@
 #include <geos/algorithm/Orientation.h>
 #include <geos/algorithm/CGAlgorithmsDD.h>
 #include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/CoordinateArraySequence.h>
 #include <geos/geom/Coordinate.h>
 #include <geos/geom/Location.h>
 #include <geos/util/IllegalArgumentException.h>
@@ -38,6 +39,101 @@ Orientation::index(const geom::Coordinate& p1, const geom::Coordinate& p2,
     return CGAlgorithmsDD::orientationIndex(p1, p2, q);
 }
 
+
+/* public static */
+bool
+Orientation::isCCW(const geom::CoordinateSequence* ring)
+{
+    // # of points without closing endpoint
+    int nPts = ring->size() - 1;
+    // sanity check
+    if (nPts < 3)
+        throw util::IllegalArgumentException(
+            "Ring has fewer than 4 points, so orientation cannot be determined");
+
+    /**
+     * Find first highest point after a lower point, if one exists
+     * (e.g. a rising segment)
+     * If one does not exist, hiIndex will remain 0
+     * and the ring must be flat.
+     * Note this relies on the convention that
+     * rings have the same start and end point.
+     */
+    geom::Coordinate upHiPt;
+    ring->getAt(0, upHiPt);
+    double prevY = upHiPt.y;
+    geom::Coordinate upLowPt;
+    upLowPt.setNull();
+    // const geom::Coordinate& upLowPt = nullptr;
+    int iUpHi = 0;
+    for (int i = 1; i <= nPts; i++) {
+        double py = ring->getY(i);
+        /**
+        * If segment is upwards and endpoint is higher, record it
+        */
+        if (py > prevY && py >= upHiPt.y) {
+            ring->getAt(i, upHiPt);
+            iUpHi = i;
+            ring->getAt(i-1, upLowPt);
+        }
+        prevY = py;
+    }
+    /**
+     * Check if ring is flat and return default value if so
+     */
+    if (iUpHi == 0) return false;
+
+    /**
+     * Find the next lower point after the high point
+     * (e.g. a falling segment).
+     * This must exist since ring is not flat.
+     */
+    int iDownLow = iUpHi;
+    do {
+        iDownLow = (iDownLow + 1) % nPts;
+    } while (iDownLow != iUpHi && ring->getY(iDownLow) == upHiPt.y );
+
+    const geom::Coordinate& downLowPt = ring->getAt(iDownLow);
+    int iDownHi = iDownLow > 0 ? iDownLow - 1 : nPts - 1;
+    const geom::Coordinate& downHiPt = ring->getAt(iDownHi);
+
+    /**
+     * Two cases can occur:
+     * 1) the hiPt and the downPrevPt are the same.
+     *    This is the general position case of a "pointed cap".
+     *    The ring orientation is determined by the orientation of the cap
+     * 2) The hiPt and the downPrevPt are different.
+     *    In this case the top of the cap is flat.
+     *    The ring orientation is given by the direction of the flat segment
+     */
+    if (upHiPt.equals2D(downHiPt)) {
+      /**
+       * Check for the case where the cap has configuration A-B-A.
+       * This can happen if the ring does not contain 3 distinct points
+       * (including the case where the input array has fewer than 4 elements), or
+       * it contains coincident line segments.
+       */
+      if (upLowPt.equals2D(upHiPt) || downLowPt.equals2D(upHiPt) || upLowPt.equals2D(downLowPt))
+        return false;
+
+      /**
+       * It can happen that the top segments are coincident.
+       * This is an invalid ring, which cannot be computed correctly.
+       * In this case the orientation is 0, and the result is false.
+       */
+      int orientationIndex = index(upLowPt, upHiPt, downLowPt);
+      return orientationIndex == COUNTERCLOCKWISE;
+    }
+    else {
+      /**
+       * Flat cap - direction of flat top determines orientation
+       */
+      double delX = downHiPt.x - upHiPt.x;
+      return delX < 0;
+    }
+}
+
+#if 0
 /* public static */
 bool
 Orientation::isCCW(const geom::CoordinateSequence* ring)
@@ -122,7 +218,7 @@ Orientation::isCCW(const geom::CoordinateSequence* ring)
 
     return isCCW;
 }
-
+#endif
 
 
 } // namespace geos.algorithm
diff --git a/tests/unit/algorithm/CGAlgorithms/isCCWTest.cpp b/tests/unit/algorithm/CGAlgorithms/isCCWTest.cpp
index e972776..f6acbae 100644
--- a/tests/unit/algorithm/CGAlgorithms/isCCWTest.cpp
+++ b/tests/unit/algorithm/CGAlgorithms/isCCWTest.cpp
@@ -28,19 +28,37 @@ namespace tut {
 struct test_isccw_data {
     typedef std::unique_ptr<geos::geom::Geometry> GeometryPtr;
 
-    std::unique_ptr<geos::geom::CoordinateSequence> cs_;
     geos::io::WKTReader reader_;
     geos::io::WKBReader breader_;
 
     test_isccw_data()
-        : cs_(nullptr)
     {
-        assert(nullptr == cs_);
     }
 
     ~test_isccw_data()
     {
     }
+
+    void
+    checkOrientationCCW(bool expectedCCW, const std::string& wkt)
+    {
+        GeometryPtr geom(reader_.read(wkt));
+        geos::geom::Polygon* poly = dynamic_cast<geos::geom::Polygon*>(geom.get());
+        ensure("WKT must be POLYGON)", poly != nullptr);
+        const geos::geom::CoordinateSequence* cs = poly->getExteriorRing()->getCoordinatesRO();
+        bool actualCCW = Orientation::isCCW(cs);
+        ensure_equals("CoordinateSequence isCCW", expectedCCW, actualCCW);
+    }
+
+    void
+    checkHexOrientationCCW(bool expectedCCW, std::istringstream& wkt)
+    {
+        GeometryPtr geom(breader_.readHEX(wkt));
+        auto cs = geom->getCoordinates();
+        bool actualCCW = Orientation::isCCW(cs.get());
+        ensure_equals("CoordinateSequence isCCW", expectedCCW, actualCCW);
+    }
+
 };
 
 typedef test_group<test_isccw_data> group;
@@ -59,12 +77,7 @@ void object::test<1>
 ()
 {
     const std::string wkt("POLYGON ((60 180, 140 240, 140 240, 140 240, 200 180, 120 120, 60 180))");
-    GeometryPtr geom(reader_.read(wkt));
-
-    cs_ = geom->getCoordinates();
-    bool isCCW = Orientation::isCCW(cs_.get());
-
-    ensure_equals(false, isCCW);
+    checkOrientationCCW(false, wkt);
 }
 
 // 2 - Test if coordinates of polygon are counter-clockwise oriented
@@ -74,12 +87,7 @@ void object::test<2>
 ()
 {
     const std::string wkt("POLYGON ((60 180, 140 120, 100 180, 140 240, 60 180))");
-    GeometryPtr geom(reader_.read(wkt));
-
-    cs_ = geom->getCoordinates();
-    bool isCCW = Orientation::isCCW(cs_.get());
-
-    ensure_equals(true, isCCW);
+    checkOrientationCCW(true, wkt);
 }
 
 // 3 - Test the same polygon as in test No 2 but with duplicated top point
@@ -89,12 +97,7 @@ void object::test<3>
 ()
 {
     const std::string wkt("POLYGON ((60 180, 140 120, 100 180, 140 240, 140 240, 60 180))");
-    GeometryPtr geom(reader_.read(wkt));
-
-    cs_ = geom->getCoordinates();
-    bool isCCW = Orientation::isCCW(cs_.get());
-
-    ensure_equals(true, isCCW);
+    checkOrientationCCW(true, wkt);
 }
 
 // 4 - Test orientation the narrow (almost collapsed) ring
@@ -107,10 +110,7 @@ void object::test<4>
 {
     std::istringstream
     wkt("0102000000040000000000000000000000841D588465963540F56BFB214F0341408F26B714B2971B40F66BFB214F0341408C26B714B2971B400000000000000000841D588465963540");
-    GeometryPtr geom(breader_.readHEX(wkt));
-    cs_ = geom->getCoordinates();
-    bool isCCW = Orientation::isCCW(cs_.get());
-    ensure_equals(isCCW, true);
+    checkHexOrientationCCW(true, wkt);
 }
 
 // 5 - Test orientation the narrow (almost collapsed) ring
@@ -123,11 +123,124 @@ void object::test<5>
 {
     std::istringstream
     wkt("0102000000040000000000000000000000841D588465963540F56BFB214F0341408F26B714B2971B40F66BFB214F0341408E26B714B2971B400000000000000000841D588465963540");
-    GeometryPtr geom(breader_.readHEX(wkt));
-    cs_ = geom->getCoordinates();
-    bool isCCW = Orientation::isCCW(cs_.get());
-    ensure_equals(isCCW, true);
+    checkHexOrientationCCW(true, wkt);
+}
+
+// testCCWSmall
+template<>
+template<>
+void object::test<6>
+()
+{
+    const std::string wkt("POLYGON ((1 1, 9 1, 5 9, 1 1))");
+    checkOrientationCCW(true, wkt);
+}
+
+// testFlatTopSegment
+template<>
+template<>
+void object::test<7>
+()
+{
+    const std::string wkt("POLYGON ((100 200, 200 200, 200 100, 100 100, 100 200))");
+    checkOrientationCCW(false, wkt);
+}
+
+// testFlatMultipleTopSegment
+template<>
+template<>
+void object::test<8>
+()
+{
+    const std::string wkt("POLYGON ((100 200, 127 200, 151 200, 173 200, 200 200, 100 100, 100 200))");
+    checkOrientationCCW(false, wkt);
+}
+
+// testDegenerateRingHorizontal
+template<>
+template<>
+void object::test<9>
+()
+{
+    const std::string wkt("POLYGON ((100 200, 100 200, 200 200, 100 200))");
+    checkOrientationCCW(false, wkt);
+}
+
+// testDegenerateRingAngled
+template<>
+template<>
+void object::test<10>
+()
+{
+    const std::string wkt("POLYGON ((100 100, 100 100, 200 200, 100 100))");
+    checkOrientationCCW(false, wkt);
 }
 
+// testDegenerateRingVertical
+template<>
+template<>
+void object::test<11>
+()
+{
+    const std::string wkt("POLYGON ((200 100, 200 100, 200 200, 200 100))");
+    checkOrientationCCW(false, wkt);
+}
+
+/**
+* This case is an invalid ring, so answer is a default value
+*/
+// testTopAngledSegmentCollapse
+template<>
+template<>
+void object::test<12>
+()
+{
+    const std::string wkt("POLYGON ((10 20, 61 20, 20 30, 50 60, 10 20))");
+    checkOrientationCCW(false, wkt);
+}
+
+// testABATopFlatSegmentCollapse
+template<>
+template<>
+void object::test<13>
+()
+{
+    const std::string wkt("POLYGON ((71 0, 40 40, 70 40, 40 40, 20 0, 71 0))");
+    checkOrientationCCW(true, wkt);
+}
+
+// testABATopFlatSegmentCollapseMiddleStart
+template<>
+template<>
+void object::test<14>
+()
+{
+    const std::string wkt("POLYGON ((90 90, 50 90, 10 10, 90 10, 50 90, 90 90))");
+    checkOrientationCCW(true, wkt);
+}
+
+// testMultipleTopFlatSegmentCollapseSinglePoint
+template<>
+template<>
+void object::test<15>
+()
+{
+    const std::string wkt("POLYGON ((100 100, 200 100, 150 200, 170 200, 200 200, 100 200, 150 200, 100 100))");
+    checkOrientationCCW(true, wkt);
+}
+
+// testMultipleTopFlatSegmentCollapseFlatTop
+template<>
+template<>
+void object::test<16>
+()
+{
+    const std::string wkt("POLYGON ((10 10, 90 10, 70 70, 90 70, 10 70, 30 70, 50 70, 10 10))");
+    checkOrientationCCW(true, wkt);
+}
+
+
+
+
 } // namespace tut
 

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

Summary of changes:
 include/geos/algorithm/Orientation.h               |  35 +++--
 .../algorithm/locate/IndexedPointInAreaLocator.h   |   2 +-
 .../algorithm/locate/SimplePointInAreaLocator.h    |   8 +-
 src/algorithm/Orientation.cpp                      |  98 +++++++++++-
 tests/unit/algorithm/CGAlgorithms/isCCWTest.cpp    | 171 +++++++++++++++++----
 5 files changed, 263 insertions(+), 51 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list