[geos-commits] [SCM] GEOS branch 3.9 updated. 5c31f0f130c8bedd729fe7785c2be24854d2adfb

git at osgeo.org git at osgeo.org
Tue Apr 13 16:40:27 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.9 has been updated
       via  5c31f0f130c8bedd729fe7785c2be24854d2adfb (commit)
      from  9d4cf22fe9608c58d7fe330bd38edbc682cbfaa8 (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 5c31f0f130c8bedd729fe7785c2be24854d2adfb
Author: Martin Davis <mtnclimb at gmail.com>
Date:   Tue Apr 13 16:43:03 2021 -0700

    Fix BufferOp to avoid artifacts in certain polygon buffers

diff --git a/NEWS b/NEWS
index f157ba8..7a90f89 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,7 @@ Changes in 3.9.2
   - Fix crash in GEOSPreparedIntersects and GEOSPreparedContains for
     POINT EMPTY input (Even Rouault)
   - Fix IsValidOp to correctly report invalid nested MultiPolygons (#1112, Martin Davis)
+  - Fix BufferOp to avoid artifacts in certain polygon buffers (#1101, Martin Davis)
 
 
 Changes in 3.9.1
diff --git a/include/geos/operation/buffer/OffsetCurveSetBuilder.h b/include/geos/operation/buffer/OffsetCurveSetBuilder.h
index abe99d3..78950e6 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 ba07011..fc6ef78 100644
--- a/src/operation/buffer/OffsetCurveSetBuilder.cpp
+++ b/src/operation/buffer/OffsetCurveSetBuilder.cpp
@@ -332,9 +332,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,
@@ -358,28 +427,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 8fdd2fd..badc3b2 100644
--- a/tests/unit/operation/buffer/BufferOpTest.cpp
+++ b/tests/unit/operation/buffer/BufferOpTest.cpp
@@ -11,6 +11,7 @@
 #include <geos/geom/Coordinate.h>
 #include <geos/geom/GeometryFactory.h>
 #include <geos/geom/Geometry.h>
+#include <geos/geom/Polygon.h>
 #include <geos/algorithm/PointLocator.h>
 #include <geos/io/WKTReader.h>
 #include <geos/io/WKTWriter.h>
@@ -464,6 +465,71 @@ void object::test<15>
     ensure_equals_geometry(gresult.get(), gexpected.get());
 }
 
+// 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));
 
-} // namespace tut
+    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                                               |  1 +
 .../geos/operation/buffer/OffsetCurveSetBuilder.h  | 37 ++++++++-
 src/operation/buffer/OffsetCurveSetBuilder.cpp     | 91 ++++++++++++++++------
 tests/unit/operation/buffer/BufferOpTest.cpp       | 68 +++++++++++++++-
 4 files changed, 173 insertions(+), 24 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list