[geos-commits] [SCM] GEOS branch 3.8 updated. 8721ea66b23427c1eee2ee49318690a63684fe51

git at osgeo.org git at osgeo.org
Tue Apr 13 21:24:09 PDT 2021


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, 3.8 has been updated
       via  8721ea66b23427c1eee2ee49318690a63684fe51 (commit)
      from  c0061cfefea0116b149ba0a5af7a3dfa77e64ecc (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 8721ea66b23427c1eee2ee49318690a63684fe51
Author: Martin Davis <mtnclimb at gmail.com>
Date:   Tue Apr 13 21:24:01 2021 -0700

    Fix BufferOp to avoid artifacts in certain polygon buffers

diff --git a/NEWS b/NEWS
index a4c2d73..d877517 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,9 @@ Changes in 3.8.3
 
 - Bug fixes / improvements
 
+  - Fix BufferOp to avoid artifacts in certain polygon buffers (#1101, Martin Davis)
+
+
 
 Changes in 3.8.2
 2021-04-10
@@ -732,4 +735,3 @@ Changes in 2.0.0
   been dropped. You have to use GeometryFactory instead.
 - WKTWriter default constructor has been dropped. You need
   to initialize it with an explicit GeometryFactory
-
diff --git a/include/geos/operation/buffer/OffsetCurveSetBuilder.h b/include/geos/operation/buffer/OffsetCurveSetBuilder.h
index 5e7aad4..888a162 100644
--- a/include/geos/operation/buffer/OffsetCurveSetBuilder.h
+++ b/include/geos/operation/buffer/OffsetCurveSetBuilder.h
@@ -140,6 +140,42 @@ private:
                      geom::Location cwRightLoc);
 
     /**
+     * Tests whether the offset curve for a ring is fully inverted.
+     * An inverted ("inside-out") curve occurs in some specific situations
+     * involving a buffer distance which should result in a fully-eroded (empty) buffer.
+     * It can happen that the sides of a small, convex polygon
+     * produce offset segments which all cross one another to form
+     * a curve with inverted orientation.
+     * This happens at buffer distances slightly greater than the distance at
+     * which the buffer should disappear.
+     * The inverted curve will produce an incorrect non-empty buffer (for a shell)
+     * or an incorrect hole (for a hole).
+     * It must be discarded from the set of offset curves used in the buffer.
+     * Heuristics are used to reduce the number of cases which area checked,
+     * for efficiency and correctness.
+     * <p>
+     * See https://github.com/locationtech/jts/issues/472
+     *
+     * @param inputPts the input ring
+     * @param distance the buffer distance
+     * @param curvePts the generated offset curve
+     * @return true if the offset curve is inverted
+     */
+    static bool isRingCurveInverted(
+        const geom::CoordinateSequence* inputPts, double dist,
+        const geom::CoordinateSequence* curvePts);
+
+    /**
+     * Computes the maximum distance out of a set of points to a linestring.
+     *
+     * @param pts the points
+     * @param line the linestring vertices
+     * @return the maximum distance
+     */
+    static double maxDistance(
+        const geom::CoordinateSequence*  pts, const geom::CoordinateSequence*  line);
+
+    /**
      * The ringCoord is assumed to contain no repeated points.
      * It may be degenerate (i.e. contain only 1, 2, or 3 points).
      * In this case it has no area, and hence has a minimum diameter of 0.
@@ -216,4 +252,3 @@ public:
 #endif
 
 #endif // ndef GEOS_OP_BUFFER_OFFSETCURVESETBUILDER_H
-
diff --git a/src/operation/buffer/OffsetCurveSetBuilder.cpp b/src/operation/buffer/OffsetCurveSetBuilder.cpp
index f475644..0b716f3 100644
--- a/src/operation/buffer/OffsetCurveSetBuilder.cpp
+++ b/src/operation/buffer/OffsetCurveSetBuilder.cpp
@@ -331,9 +331,78 @@ OffsetCurveSetBuilder::addRingSide(const CoordinateSequence* coord,
     }
     std::vector<CoordinateSequence*> lineList;
     curveBuilder.getRingCurve(coord, side, offsetDistance, lineList);
+    // ASSERT: lineList contains exactly 1 curve (this is teh JTS semantics)
+    if (lineList.size() > 0) {
+        const CoordinateSequence* curve = lineList[0];
+        /**
+         * If the offset curve has inverted completely it will produce
+         * an unwanted artifact in the result, so skip it.
+         */
+        if (isRingCurveInverted(coord, offsetDistance, curve)) {
+            return;
+        }
+    }
     addCurves(lineList, leftLoc, rightLoc);
 }
 
+static const int MAX_INVERTED_RING_SIZE = 9;
+static const double NEARNESS_FACTOR = 0.99;
+
+/* private static*/
+bool
+OffsetCurveSetBuilder::isRingCurveInverted(
+    const CoordinateSequence* inputPts, double dist,
+    const CoordinateSequence* curvePts)
+{
+    if (dist == 0.0) return false;
+    /**
+     * Only proper rings can invert.
+     */
+    if (inputPts->size() <= 3) return false;
+    /**
+     * Heuristic based on low chance that a ring with many vertices will invert.
+     * This low limit ensures this test is fairly efficient.
+     */
+    if (inputPts->size() >= MAX_INVERTED_RING_SIZE) return false;
+
+    /**
+     * An inverted curve has no more points than the input ring.
+     * This also eliminates concave inputs (which will produce fillet arcs)
+     */
+    if (curvePts->size() > inputPts->size()) return false;
+
+    /**
+     * Check if the curve vertices are all closer to the input ring
+     * than the buffer distance.
+     * If so, the curve is NOT a valid buffer curve.
+     */
+    double distTol = NEARNESS_FACTOR * fabs(dist);
+    double maxDist = maxDistance(curvePts, inputPts);
+    bool isCurveTooClose = maxDist < distTol;
+    return isCurveTooClose;
+}
+
+/**
+ * Computes the maximum distance out of a set of points to a linestring.
+ *
+ * @param pts the points
+ * @param line the linestring vertices
+ * @return the maximum distance
+ */
+/* private static */
+double
+OffsetCurveSetBuilder::maxDistance(const CoordinateSequence*  pts, const CoordinateSequence*  line) {
+    double maxDistance = 0;
+    for (size_t i = 0; i < pts->size(); i++) {
+        Coordinate p = pts->getAt(i);
+        double dist = Distance::pointToSegmentString(p, line);
+        if (dist > maxDistance) {
+        maxDistance = dist;
+        }
+    }
+    return maxDistance;
+}
+
 /*private*/
 bool
 OffsetCurveSetBuilder::isErodedCompletely(const LinearRing* ring,
@@ -357,28 +426,6 @@ OffsetCurveSetBuilder::isErodedCompletely(const LinearRing* ring,
     if(bufferDistance < 0.0 && 2 * std::abs(bufferDistance) > envMinDimension) {
         return true;
     }
-
-    /**
-     * The following is a heuristic test to determine whether an
-     * inside buffer will be eroded completely->
-     * It is based on the fact that the minimum diameter of the ring
-     * pointset
-     * provides an upper bound on the buffer distance which would erode the
-     * ring->
-     * If the buffer distance is less than the minimum diameter, the ring
-     * may still be eroded, but this will be determined by
-     * a full topological computation->
-     *
-     */
-
-    /* MD  7 Feb 2005 - there's an unknown bug in the MD code,
-     so disable this for now */
-#if 0
-    MinimumDiameter md(ring); //=new MinimumDiameter(ring);
-    double minDiam = md.getLength();
-    return minDiam < (2 * std::fabs(bufferDistance));
-#endif
-
     return false;
 }
 
diff --git a/tests/unit/operation/buffer/BufferOpTest.cpp b/tests/unit/operation/buffer/BufferOpTest.cpp
index cef4333..33322ab 100644
--- a/tests/unit/operation/buffer/BufferOpTest.cpp
+++ b/tests/unit/operation/buffer/BufferOpTest.cpp
@@ -394,5 +394,72 @@ void object::test<12>
     ensure(gBuffer->getNumPoints() >= std::size_t(expected_segments));
 }
 
-} // namespace tut
+// Test for #1101 - Non-empty negative buffer of 4-pt convex polygon
+template<>
+template<>
+void object::test<16>
+()
+{
+    std::string wkt0("POLYGON ((666360.09 429614.71, 666344.4 429597.12, 666358.47 429584.52, 666374.5 429602.33, 666360.09 429614.71))");
+    GeomPtr g0(wktreader.read(wkt0));
+
+    ensure_not( g0->buffer( -9 )->isEmpty() );
+    ensure( g0->buffer( -10 )->isEmpty() );
+    ensure( g0->buffer( -15 )->isEmpty() );
+    ensure( g0->buffer( -18 )->isEmpty() );
+}
 
+// Test for #1101 - Non-empty negative buffer of 5-pt convex polygon
+template<>
+template<>
+void object::test<17>
+()
+{
+    std::string wkt0("POLYGON ((6 20, 16 20, 21 9, 9 0, 0 10, 6 20))");
+    GeomPtr g0(wktreader.read(wkt0));
+
+    ensure_not( g0->buffer( -8 )->isEmpty() );
+    ensure( g0->buffer( -8.6 )->isEmpty() );
+    ensure( g0->buffer( -9.6 )->isEmpty() );
+    ensure( g0->buffer( -11 )->isEmpty() );
+}
+
+// Test for #1101 - Buffer of Polygon with hole with hole eroded
+template<>
+template<>
+void object::test<18>
+()
+{
+    std::string wkt0("POLYGON ((-6 26, 29 26, 29 -5, -6 -5, -6 26), (6 20, 16 20, 21 9, 9 0, 0 10, 6 20))");
+    GeomPtr g0(wktreader.read(wkt0));
+
+    GeomPtr result1 = g0->buffer( -8 );
+    ensure( 0 == dynamic_cast<const geos::geom::Polygon*>(result1.get())->getNumInteriorRing() );
+
+    GeomPtr result2 = g0->buffer( -8.6 );
+    ensure( 0 == dynamic_cast<const geos::geom::Polygon*>(result2.get())->getNumInteriorRing() );
+
+    GeomPtr result3 = g0->buffer( -9.6 );
+    ensure( 0 == dynamic_cast<const geos::geom::Polygon*>(result2.get())->getNumInteriorRing() );
+
+    GeomPtr result4 = g0->buffer( -11 );
+    ensure( 0 == dynamic_cast<const geos::geom::Polygon*>(result2.get())->getNumInteriorRing() );
+}
+
+// Test for #1101 - Non-empty negative buffer of 5-pt convex polygon
+template<>
+template<>
+void object::test<19>
+()
+{
+    std::string wkt0("MULTIPOLYGON (((30 18, 14 0, 0 13, 16 30, 30 18)), ((180 210, 60 50, 154 6, 270 40, 290 130, 250 190, 180 210)))");
+    GeomPtr g0(wktreader.read(wkt0));
+
+    ensure( 2 == g0->buffer( -9 )->getNumGeometries() );
+    ensure( 1 == g0->buffer( -10 )->getNumGeometries() );
+    ensure( 1 == g0->buffer( -15 )->getNumGeometries() );
+    ensure( 1 == g0->buffer( -18 )->getNumGeometries() );
+}
+
+
+} // namespace tut

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

Summary of changes:
 NEWS                                               |  4 +-
 .../geos/operation/buffer/OffsetCurveSetBuilder.h  | 37 ++++++++-
 src/operation/buffer/OffsetCurveSetBuilder.cpp     | 91 ++++++++++++++++------
 tests/unit/operation/buffer/BufferOpTest.cpp       | 69 +++++++++++++++-
 4 files changed, 176 insertions(+), 25 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list