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

git at osgeo.org git at osgeo.org
Tue Jun 11 16:52:28 PDT 2019


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  ea8bde0b96494978e31624d7693eb8aebb148271 (commit)
       via  8c4251685c937d6847ef92eee489a584a5c673ea (commit)
       via  24861420afdef90559dc06f10377a9b26afd6f1c (commit)
       via  f378ac5348002ce611a360eaf59a8bb281ecfa7b (commit)
       via  a2030535758d04aa64e6fb2ad21eb1d413aad4b9 (commit)
       via  2a07cac2ad2e7c3efadd31d0abf3a95565313997 (commit)
       via  513ca427ad712d3aeaebb6c1ff5d7f7fac37001e (commit)
       via  52cf6c4d8a5db5db3d0aba3e7358c5f8d98e337a (commit)
       via  c8735c3674b5613890c51be8180c193a61a73c81 (commit)
       via  4010bac2d20d87a4b5888fb0f0a30c084e5c6a6c (commit)
       via  9d85434cb937f2e837e2c10cbee44817a4cd81e6 (commit)
       via  64f3ec8663bd210144fe2fc563688e7cb0f42f94 (commit)
       via  4d61fb5d4739fdb84975792d29465a009bca0550 (commit)
      from  d6364a4fc1d386d076b59f045fb382e7532fab96 (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 ea8bde0b96494978e31624d7693eb8aebb148271
Merge: d6364a4 8c42516
Author: Daniel Baston <dbaston at gmail.com>
Date:   Tue Jun 11 19:52:04 2019 -0400

    Merge branch 'pip-performance'


commit 8c4251685c937d6847ef92eee489a584a5c673ea
Author: Daniel Baston <dbaston at gmail.com>
Date:   Mon Jun 10 20:19:20 2019 -0400

    Factor out AbstractPreparedPolygonContains::evalPointTestGeom

diff --git a/include/geos/geom/prep/AbstractPreparedPolygonContains.h b/include/geos/geom/prep/AbstractPreparedPolygonContains.h
index d9c00cc..9325bfa 100644
--- a/include/geos/geom/prep/AbstractPreparedPolygonContains.h
+++ b/include/geos/geom/prep/AbstractPreparedPolygonContains.h
@@ -99,6 +99,16 @@ protected:
     bool eval(const geom::Geometry* geom);
 
     /**
+     * Evaluate the <tt>contains</tt> or <tt>covers</tt> relationship
+     * for the given Puntal geometry.
+     *
+     * @param geom the test geometry
+     * @param outermostLoc outermost Location of all points in geom
+     * @return true if the test geometry is contained/covered in the target
+     */
+    bool evalPointTestGeom(const geom::Geometry* geom, geom::Location::Value outermostLoc);
+
+    /**
      * Computes the full topological predicate.
      * Used when short-circuit tests are not conclusive.
      *
diff --git a/src/geom/prep/AbstractPreparedPolygonContains.cpp b/src/geom/prep/AbstractPreparedPolygonContains.cpp
index 5f736b6..2cf2c6a 100644
--- a/src/geom/prep/AbstractPreparedPolygonContains.cpp
+++ b/src/geom/prep/AbstractPreparedPolygonContains.cpp
@@ -111,38 +111,17 @@ AbstractPreparedPolygonContains::eval(const geom::Geometry* geom)
     // in a quick negative result.
     auto outermostLoc = getOutermostTestComponentLocation(geom);
 
+    // Short-circuit for (Multi)Points
+    if (geom->getDimension() == 0) {
+        return evalPointTestGeom(geom, outermostLoc);
+    }
+
     if (outermostLoc == Location::EXTERIOR) {
         // If a point of any test components does not lie in target,
         // result is false
         return false;
     }
 
-    // Short-circuit for (Multi)Points
-    //
-    // For the Covers predicate, we can return true
-    // since no Points are on the exterior of the Polygon.
-    //
-    // For the Contains predicate, we need to test if any
-    // of those points lie in the interior of the target
-    // geometry.
-    //
-    // If so, the test is contained.
-    // If not, all points are on the boundary of the area,
-    // which implies not contained.
-    if (geom->getDimension() == 0) {
-        if (requireSomePointInInterior && outermostLoc == Location::BOUNDARY) {
-            if (geom->getNumGeometries() > 1) {
-                return isAnyTestComponentInTargetInterior(geom);
-            } else {
-                // We only had one point, so if it was on the
-                // boundary, it is not contained.
-                return false;
-            }
-        } else {
-            return true;
-        }
-    }
-
     // Check if there is any intersection between the line segments
     // in target and test.
     // In some important cases, finding a proper interesection implies that the
@@ -202,6 +181,37 @@ AbstractPreparedPolygonContains::eval(const geom::Geometry* geom)
     return true;
 }
 
+bool AbstractPreparedPolygonContains::evalPointTestGeom(const Geometry *geom,
+                                                      Location::Value outermostLoc) {
+    // If we had a point on the ourside of the polygon,
+    // we aren't covered or contained.
+    if (outermostLoc == Location::EXTERIOR) {
+        return false;
+    }
+
+    // For the Covers predicate, we can return true
+    // since no Points are on the exterior of the target
+    // geometry.
+    if (!requireSomePointInInterior) {
+        return true;
+    }
+
+    // For the Contains predicate, we need to test if any
+    // of those points lie in the interior of the target
+    // geometry.
+    if (outermostLoc == Location::INTERIOR) {
+        return true;
+    }
+
+    if (geom->getNumGeometries() > 1) {
+        // for MultiPoint, try to find at least one point
+        // in interior
+        return isAnyTestComponentInTargetInterior(geom);
+    }
+
+    return false;
+}
+
 //
 // public:
 //

commit 24861420afdef90559dc06f10377a9b26afd6f1c
Author: Daniel Baston <dbaston at gmail.com>
Date:   Mon Jun 10 17:17:19 2019 -0400

    Rename location filters

diff --git a/src/geom/prep/PreparedPolygonPredicate.cpp b/src/geom/prep/PreparedPolygonPredicate.cpp
index c79cd88..886193c 100644
--- a/src/geom/prep/PreparedPolygonPredicate.cpp
+++ b/src/geom/prep/PreparedPolygonPredicate.cpp
@@ -38,8 +38,8 @@ namespace prep { // geos.geom.prep
 //
 // protected:
 //
-struct AnyMatchingLocationFilter : public GeometryComponentFilter {
-    explicit AnyMatchingLocationFilter(algorithm::locate::PointOnGeometryLocator* locator, int loc) :
+struct LocationMatchingFilter : public GeometryComponentFilter {
+    explicit LocationMatchingFilter(algorithm::locate::PointOnGeometryLocator* locator, int loc) :
         pt_locator(locator), test_loc(loc), found(false) {}
 
     algorithm::locate::PointOnGeometryLocator* pt_locator;
@@ -60,8 +60,8 @@ struct AnyMatchingLocationFilter : public GeometryComponentFilter {
     }
 };
 
-struct AnyNotMatchingLocationFilter : public GeometryComponentFilter {
-    explicit AnyNotMatchingLocationFilter(algorithm::locate::PointOnGeometryLocator* locator, int loc) :
+struct LocationNotMatchingFilter : public GeometryComponentFilter {
+    explicit LocationNotMatchingFilter(algorithm::locate::PointOnGeometryLocator* locator, int loc) :
             pt_locator(locator), test_loc(loc), found(false) {}
 
     algorithm::locate::PointOnGeometryLocator* pt_locator;
@@ -126,7 +126,7 @@ bool
 PreparedPolygonPredicate::isAllTestComponentsInTargetInterior(
     const geom::Geometry* testGeom) const
 {
-    AnyNotMatchingLocationFilter filter(prepPoly->getPointLocator(), geom::Location::INTERIOR);
+    LocationNotMatchingFilter filter(prepPoly->getPointLocator(), geom::Location::INTERIOR);
     testGeom->apply_ro(&filter);
 
     return !filter.found;
@@ -136,7 +136,7 @@ bool
 PreparedPolygonPredicate::isAnyTestComponentInTarget(
     const geom::Geometry* testGeom) const
 {
-    AnyNotMatchingLocationFilter filter(prepPoly->getPointLocator(), geom::Location::EXTERIOR);
+    LocationNotMatchingFilter filter(prepPoly->getPointLocator(), geom::Location::EXTERIOR);
     testGeom->apply_ro(&filter);
 
     return filter.found;
@@ -146,7 +146,7 @@ bool
 PreparedPolygonPredicate::isAnyTestComponentInTargetInterior(
     const geom::Geometry* testGeom) const
 {
-    AnyMatchingLocationFilter filter(prepPoly->getPointLocator(), geom::Location::INTERIOR);
+    LocationMatchingFilter filter(prepPoly->getPointLocator(), geom::Location::INTERIOR);
     testGeom->apply_ro(&filter);
 
     return filter.found;

commit f378ac5348002ce611a360eaf59a8bb281ecfa7b
Author: Daniel Baston <dbaston at gmail.com>
Date:   Mon Jun 10 15:09:56 2019 -0400

    Avoid relying on numeric ordering of Location

diff --git a/src/geom/prep/PreparedPolygonPredicate.cpp b/src/geom/prep/PreparedPolygonPredicate.cpp
index f798efb..c79cd88 100644
--- a/src/geom/prep/PreparedPolygonPredicate.cpp
+++ b/src/geom/prep/PreparedPolygonPredicate.cpp
@@ -26,7 +26,6 @@
 #include <geos/algorithm/locate/PointOnGeometryLocator.h>
 #include <geos/algorithm/locate/SimplePointInAreaLocator.h>
 // std
-#include <algorithm> // std::max
 #include <cstddef>
 
 namespace geos {
@@ -86,29 +85,31 @@ struct AnyNotMatchingLocationFilter : public GeometryComponentFilter {
 struct OutermostLocationFilter : public GeometryComponentFilter {
     explicit OutermostLocationFilter(algorithm::locate::PointOnGeometryLocator* locator) :
     pt_locator(locator),
-    max_loc(geom::Location::UNDEF),
-    found(false) {}
+    outermost_loc(geom::Location::UNDEF),
+    done(false) {}
 
     algorithm::locate::PointOnGeometryLocator* pt_locator;
-    Location::Value max_loc;
-    bool found;
+    Location::Value outermost_loc;
+    bool done;
 
     void filter_ro(const Geometry* g) override {
         const Coordinate* pt = g->getCoordinate();
         auto loc = static_cast<Location::Value>(pt_locator->locate(pt));
 
-        max_loc = std::max(max_loc, loc);
-        if (max_loc == geom::Location::EXTERIOR) {
-            found = true;
+        if (outermost_loc == Location::UNDEF || outermost_loc == Location::INTERIOR) {
+            outermost_loc = loc;
+        } else if (Location::EXTERIOR) {
+            outermost_loc = loc;
+            done = true;
         }
     }
 
     bool isDone() override {
-        return found;
+        return done;
     }
 
     Location::Value getOutermostLocation() {
-        return max_loc;
+        return outermost_loc;
     }
 };
 

commit a2030535758d04aa64e6fb2ad21eb1d413aad4b9
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sun Jun 9 22:03:47 2019 -0400

    Clarify PIP short-circuits in PreparedPolygonPredicate

diff --git a/include/geos/geom/prep/PreparedPolygonPredicate.h b/include/geos/geom/prep/PreparedPolygonPredicate.h
index d100ebe..d983982 100644
--- a/include/geos/geom/prep/PreparedPolygonPredicate.h
+++ b/include/geos/geom/prep/PreparedPolygonPredicate.h
@@ -22,6 +22,7 @@
 #define GEOS_GEOM_PREP_PREPAREDPOLYGONPREDICATE_H
 
 #include <geos/geom/Coordinate.h>
+#include <geos/geom/Location.h>
 
 // forward declarations
 namespace geos {
@@ -64,16 +65,13 @@ protected:
     const PreparedPolygon* const prepPoly;
 
     /** \brief
-     * Tests whether all components of the test Geometry
-     * are contained in the target geometry.
-     *
-     * Handles both linear and point components.
+     * Returns the outermost Location among a test point from each
+     * components of the test geometry.
      *
      * @param testGeom a geometry to test
-     * @return true if all components of the argument are contained
-     *              in the target geometry
+     * @return the outermost Location
      */
-    bool isAllTestComponentsInTarget(const geom::Geometry* testGeom) const;
+    geom::Location::Value getOutermostTestComponentLocation(const geom::Geometry* testGeom) const;
 
     /** \brief
      * Tests whether all components of the test Geometry
diff --git a/src/geom/prep/AbstractPreparedPolygonContains.cpp b/src/geom/prep/AbstractPreparedPolygonContains.cpp
index 4cc8f58..5f736b6 100644
--- a/src/geom/prep/AbstractPreparedPolygonContains.cpp
+++ b/src/geom/prep/AbstractPreparedPolygonContains.cpp
@@ -109,25 +109,38 @@ AbstractPreparedPolygonContains::eval(const geom::Geometry* geom)
 {
     // Do point-in-poly tests first, since they are cheaper and may result
     // in a quick negative result.
-    //
-    // If a point of any test components does not lie in target,
-    // result is false
-    if (!requireSomePointInInterior || geom->getGeometryTypeId() != GEOS_POINT) {
-        bool isAllInTargetArea = isAllTestComponentsInTarget(geom);
-        if (!isAllInTargetArea) {
-            return false;
-        }
+    auto outermostLoc = getOutermostTestComponentLocation(geom);
+
+    if (outermostLoc == Location::EXTERIOR) {
+        // If a point of any test components does not lie in target,
+        // result is false
+        return false;
     }
 
-    // If the test geometry consists of only Points,
-    // then it is now sufficient to test if any of those
-    // points lie in the interior of the target geometry.
+    // Short-circuit for (Multi)Points
+    //
+    // For the Covers predicate, we can return true
+    // since no Points are on the exterior of the Polygon.
+    //
+    // For the Contains predicate, we need to test if any
+    // of those points lie in the interior of the target
+    // geometry.
+    //
     // If so, the test is contained.
     // If not, all points are on the boundary of the area,
     // which implies not contained.
-    if(requireSomePointInInterior && geom->getDimension() == 0) {
-        bool isAnyInTargetInterior = isAnyTestComponentInTargetInterior(geom);
-        return isAnyInTargetInterior;
+    if (geom->getDimension() == 0) {
+        if (requireSomePointInInterior && outermostLoc == Location::BOUNDARY) {
+            if (geom->getNumGeometries() > 1) {
+                return isAnyTestComponentInTargetInterior(geom);
+            } else {
+                // We only had one point, so if it was on the
+                // boundary, it is not contained.
+                return false;
+            }
+        } else {
+            return true;
+        }
     }
 
     // Check if there is any intersection between the line segments
diff --git a/src/geom/prep/PreparedPolygonPredicate.cpp b/src/geom/prep/PreparedPolygonPredicate.cpp
index 5944f47..f798efb 100644
--- a/src/geom/prep/PreparedPolygonPredicate.cpp
+++ b/src/geom/prep/PreparedPolygonPredicate.cpp
@@ -26,6 +26,7 @@
 #include <geos/algorithm/locate/PointOnGeometryLocator.h>
 #include <geos/algorithm/locate/SimplePointInAreaLocator.h>
 // std
+#include <algorithm> // std::max
 #include <cstddef>
 
 namespace geos {
@@ -82,13 +83,42 @@ struct AnyNotMatchingLocationFilter : public GeometryComponentFilter {
     }
 };
 
-bool
-PreparedPolygonPredicate::isAllTestComponentsInTarget(const geom::Geometry* testGeom) const
+struct OutermostLocationFilter : public GeometryComponentFilter {
+    explicit OutermostLocationFilter(algorithm::locate::PointOnGeometryLocator* locator) :
+    pt_locator(locator),
+    max_loc(geom::Location::UNDEF),
+    found(false) {}
+
+    algorithm::locate::PointOnGeometryLocator* pt_locator;
+    Location::Value max_loc;
+    bool found;
+
+    void filter_ro(const Geometry* g) override {
+        const Coordinate* pt = g->getCoordinate();
+        auto loc = static_cast<Location::Value>(pt_locator->locate(pt));
+
+        max_loc = std::max(max_loc, loc);
+        if (max_loc == geom::Location::EXTERIOR) {
+            found = true;
+        }
+    }
+
+    bool isDone() override {
+        return found;
+    }
+
+    Location::Value getOutermostLocation() {
+        return max_loc;
+    }
+};
+
+Location::Value
+PreparedPolygonPredicate::getOutermostTestComponentLocation(const geom::Geometry* testGeom) const
 {
-    AnyMatchingLocationFilter filter(prepPoly->getPointLocator(), geom::Location::EXTERIOR);
+    OutermostLocationFilter filter(prepPoly->getPointLocator());
     testGeom->apply_ro(&filter);
 
-    return !filter.found;
+    return filter.getOutermostLocation();
 }
 
 bool

commit 2a07cac2ad2e7c3efadd31d0abf3a95565313997
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sun Jun 9 22:03:36 2019 -0400

    Add some prepared polygon predicate tests

diff --git a/tests/xmltester/tests/general/TestPreparedPolygonPredicate.xml b/tests/xmltester/tests/general/TestPreparedPolygonPredicate.xml
index 04ca2e6..c4e2a8d 100644
--- a/tests/xmltester/tests/general/TestPreparedPolygonPredicate.xml
+++ b/tests/xmltester/tests/general/TestPreparedPolygonPredicate.xml
@@ -18,7 +18,21 @@
 </case>
 
 <case>
-  <desc>A/P - point equal to start point of polygon
+  <desc>A/P - point in polygon interior
+  </desc>
+  <a>
+    POLYGON ((10 10, 60 100, 110 10, 10 10))
+  </a>
+  <b>
+    POINT (20 20)
+  </b>
+  <test>  <op name="contains"   		arg1="A" arg2="B">   true  </op> </test>
+  <test>  <op name="covers"     		arg1="A" arg2="B">   true  </op> </test>
+  <test>  <op name="intersects" 		arg1="A" arg2="B">   true  </op> </test>
+</case>
+
+<case>
+  <desc>A/P - point outside of polygon
   </desc>
   <a>
     POLYGON ((10 10, 60 100, 110 10, 10 10))
@@ -32,6 +46,76 @@
 </case>
 
 <case>
+  <desc>A/mP - both points equal to polygon vertices
+  </desc>
+  <a>
+    POLYGON ((10 10, 60 100, 110 10, 10 10))
+  </a>
+  <b>
+    MULTIPOINT ((10 10), (60 100))
+  </b>
+  <test>  <op name="contains"   		arg1="A" arg2="B">   false  </op> </test>
+  <test>  <op name="covers"     		arg1="A" arg2="B">   true   </op> </test>
+  <test>  <op name="intersects" 		arg1="A" arg2="B">   true   </op> </test>
+</case>
+
+<case>
+  <desc>A/mP - both points in polygon interior
+  </desc>
+  <a>
+    POLYGON ((10 10, 60 100, 110 10, 10 10))
+  </a>
+  <b>
+    MULTIPOINT ((20 20), (21 21))
+  </b>
+  <test>  <op name="contains"   		arg1="A" arg2="B">   true   </op> </test>
+  <test>  <op name="covers"     		arg1="A" arg2="B">   true   </op> </test>
+  <test>  <op name="intersects" 		arg1="A" arg2="B">   true   </op> </test>
+</case>
+
+<case>
+  <desc>A/mP - one point interior, one point equal to a polygon vertex
+  </desc>
+  <a>
+    POLYGON ((10 10, 60 100, 110 10, 10 10))
+  </a>
+  <b>
+    MULTIPOINT ((60 100), (21 21))
+  </b>
+  <test>  <op name="contains"   		arg1="A" arg2="B">   true   </op> </test>
+  <test>  <op name="covers"     		arg1="A" arg2="B">   true   </op> </test>
+  <test>  <op name="intersects" 		arg1="A" arg2="B">   true   </op> </test>
+</case>
+
+<case>
+  <desc>A/mP - one point interior, one point exterior
+  </desc>
+  <a>
+    POLYGON ((10 10, 60 100, 110 10, 10 10))
+  </a>
+  <b>
+    MULTIPOINT ((20 20), (500 500))
+  </b>
+  <test>  <op name="contains"   		arg1="A" arg2="B">   false   </op> </test>
+  <test>  <op name="covers"     		arg1="A" arg2="B">   false   </op> </test>
+  <test>  <op name="intersects" 		arg1="A" arg2="B">   true   </op> </test>
+</case>
+
+<case>
+  <desc>A/mP - one point equal to a polygon vertex, one point exterior
+  </desc>
+  <a>
+    POLYGON ((10 10, 60 100, 110 10, 10 10))
+  </a>
+  <b>
+    MULTIPOINT ((10 10), (500 500))
+  </b>
+  <test>  <op name="contains"   		arg1="A" arg2="B">   false   </op> </test>
+  <test>  <op name="covers"     		arg1="A" arg2="B">   false   </op> </test>
+  <test>  <op name="intersects" 		arg1="A" arg2="B">   true   </op> </test>
+</case>
+
+<case>
   <desc>mA/L
   	A has 2 shells touching at one vertex and one non-vertex.
   	B passes between the shells, but is wholely contained

commit 513ca427ad712d3aeaebb6c1ff5d7f7fac37001e
Author: Daniel Baston <dbaston at gmail.com>
Date:   Thu Jun 6 20:26:56 2019 -0400

    Add GeometryComponentFilter::isDone()

diff --git a/include/geos/geom/GeometryComponentFilter.h b/include/geos/geom/GeometryComponentFilter.h
index 514b64f..4e485fe 100644
--- a/include/geos/geom/GeometryComponentFilter.h
+++ b/include/geos/geom/GeometryComponentFilter.h
@@ -52,6 +52,8 @@ public:
     virtual void filter_rw(Geometry* geom);
     virtual void filter_ro(const Geometry* geom);
 
+    virtual bool isDone() { return false; }
+
     virtual
     ~GeometryComponentFilter() {}
 };
diff --git a/src/geom/GeometryCollection.cpp b/src/geom/GeometryCollection.cpp
index e00a63f..51809c9 100644
--- a/src/geom/GeometryCollection.cpp
+++ b/src/geom/GeometryCollection.cpp
@@ -310,7 +310,7 @@ void
 GeometryCollection::apply_rw(GeometryComponentFilter* filter)
 {
     filter->filter_rw(this);
-    for(size_t i = 0; i < geometries->size(); ++i) {
+    for(size_t i = 0; i < geometries->size() && !filter->isDone(); ++i) {
         (*geometries)[i]->apply_rw(filter);
     }
 }
@@ -319,7 +319,7 @@ void
 GeometryCollection::apply_ro(GeometryComponentFilter* filter) const
 {
     filter->filter_ro(this);
-    for(size_t i = 0; i < geometries->size(); ++i) {
+    for(size_t i = 0; i < geometries->size() && !filter->isDone(); ++i) {
         (*geometries)[i]->apply_ro(filter);
     }
 }
diff --git a/src/geom/Polygon.cpp b/src/geom/Polygon.cpp
index 779f46b..586a6dd 100644
--- a/src/geom/Polygon.cpp
+++ b/src/geom/Polygon.cpp
@@ -394,7 +394,7 @@ Polygon::apply_ro(GeometryComponentFilter* filter) const
 {
     filter->filter_ro(this);
     shell->apply_ro(filter);
-    for(size_t i = 0, n = holes->size(); i < n; ++i) {
+    for(size_t i = 0, n = holes->size(); i < n && !filter->isDone(); ++i) {
         (*holes)[i]->apply_ro(filter);
     }
 }
@@ -404,7 +404,7 @@ Polygon::apply_rw(GeometryComponentFilter* filter)
 {
     filter->filter_rw(this);
     shell->apply_rw(filter);
-    for(size_t i = 0, n = holes->size(); i < n; ++i) {
+    for(size_t i = 0, n = holes->size(); i < n && !filter->isDone(); ++i) {
         (*holes)[i]->apply_rw(filter);
     }
 }
diff --git a/src/geom/prep/PreparedPolygonPredicate.cpp b/src/geom/prep/PreparedPolygonPredicate.cpp
index 261312e..5944f47 100644
--- a/src/geom/prep/PreparedPolygonPredicate.cpp
+++ b/src/geom/prep/PreparedPolygonPredicate.cpp
@@ -47,14 +47,17 @@ struct AnyMatchingLocationFilter : public GeometryComponentFilter {
     bool found;
 
     void filter_ro(const Geometry* g) override {
-        auto pt = g->getCoordinate();
-        if (!found) {
-            const int loc = pt_locator->locate(pt);
-            if (loc == test_loc) {
-                found = true;
-            }
+        const Coordinate* pt = g->getCoordinate();
+        const int loc = pt_locator->locate(pt);
+
+        if (loc == test_loc) {
+            found = true;
         }
     }
+
+    bool isDone() override {
+        return found;
+    }
 };
 
 struct AnyNotMatchingLocationFilter : public GeometryComponentFilter {
@@ -66,14 +69,17 @@ struct AnyNotMatchingLocationFilter : public GeometryComponentFilter {
     bool found;
 
     void filter_ro(const Geometry* g) override {
-        auto pt = g->getCoordinate();
-        if (!found) {
-            const int loc = pt_locator->locate(pt);
-            if (loc != test_loc) {
-                found = true;
-            }
+        const Coordinate* pt = g->getCoordinate();
+        const int loc = pt_locator->locate(pt);
+
+        if (loc != test_loc) {
+            found = true;
         }
     }
+
+    bool isDone() override {
+        return found;
+    }
 };
 
 bool
diff --git a/tests/unit/geom/GeometryComponentFilterTest.cpp b/tests/unit/geom/GeometryComponentFilterTest.cpp
index 7af0830..29127c7 100644
--- a/tests/unit/geom/GeometryComponentFilterTest.cpp
+++ b/tests/unit/geom/GeometryComponentFilterTest.cpp
@@ -4,6 +4,8 @@
 #include <tut/tut.hpp>
 // geos
 #include <geos/geom/Geometry.h>
+#include <geos/geom/Polygon.h>
+#include <geos/geom/LineString.h>
 #include <geos/geom/GeometryFactory.h>
 #include <geos/geom/GeometryComponentFilter.h>
 #include <geos/io/WKTReader.h>
@@ -35,6 +37,34 @@ group test_geometrycomponentfilter_group("geos::geom::GeometryComponentFilter");
 // Test Cases
 //
 
+class NumPointsInFirstNComponents : public geos::geom::GeometryComponentFilter {
+public:
+
+    NumPointsInFirstNComponents(size_t n) : components_remaining(n), num_points(0) {}
+
+    void
+    filter_ro(const geos::geom::Geometry* g) override {
+        num_points += g->getNumPoints();
+        components_remaining--;
+    }
+
+    void
+    filter_rw(geos::geom::Geometry* g) override {
+        filter_ro(g);
+    }
+
+    size_t numPoints() {
+        return num_points;
+    }
+
+    bool isDone() override {
+        return components_remaining == 0;
+    }
+private:
+    size_t components_remaining;
+    size_t num_points;
+};
+
 // Split components into two categories: Lineal and all other types
 template<>
 template<>
@@ -94,4 +124,62 @@ void object::test<1>
     ensure_equals(lineal.size() + nonlineal.size(), 8ul);
 }
 
+template<>
+template<>
+void object::test<2>()
+{
+    // Test isDone() behavior for collections
+
+    // collection of 4 geometries
+    GeometryPtr g(reader.read("GEOMETRYCOLLECTION("
+                              "POINT(0 0),"
+                              "LINESTRING(0 0,1 1,1 2),"
+                              "POLYGON((0 0,4 0,4 4,0 4,0 0)),"
+                              "MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4)))"));
+
+    auto filter = NumPointsInFirstNComponents(3);
+    g->apply_ro(&filter);
+
+    // Current GeometryComponentFilter semantics have the filter visit the entire collection
+    // in addition to each of its components. So the first 3 components are the the entire collection,
+    // the Point, and the LineString
+    ensure_equals(g->getNumPoints() + g->getGeometryN(0)->getNumPoints() + g->getGeometryN(1)->getNumPoints(),
+            filter.numPoints());
+
+    // isDone() behavior should be the same in apply_rw scenario.
+    filter = NumPointsInFirstNComponents(2);
+    g->apply_rw(&filter);
+    ensure_equals(g->getNumPoints() + g->getGeometryN(0)->getNumPoints(),
+            filter.numPoints());
+}
+
+template<>
+template<>
+void object::test<3>()
+{
+    // Test isDone() behavior for polygons
+
+    // collection of 4 geometries
+    GeometryPtr g(reader.read("POLYGON((0 0, 100 0, 100 100, 0 100, 0 0),"  // 5 pt exterior ring
+                              "(1 1, 2 1, 2 2, 1 1),"                       // 4 pt interior ring
+                              "(5 5, 5 8, 8 8, 7 7, 6 6, 5 5))"));          // 6 pt interior ring
+    auto poly = dynamic_cast<geos::geom::Polygon*>(g.get());
+
+    auto filter = NumPointsInFirstNComponents(3);
+    poly->apply_ro(&filter);
+
+    // Current GeometryComponentFilter semantics have the filter visit the entire polygon
+    // in addition to each of its rings. So the first 3 components are the the entire polygon,
+    // the the exterior ring, and the first interior ring.
+    ensure_equals(poly->getNumPoints() + poly->getExteriorRing()->getNumPoints() + poly->getInteriorRingN(0)->getNumPoints(),
+                  filter.numPoints());
+
+    // isDone() behavior should be the same in apply_rw scenario.
+    filter = NumPointsInFirstNComponents(2);
+    g->apply_rw(&filter);
+    ensure_equals(poly->getNumPoints() + poly->getExteriorRing()->getNumPoints(),
+                  filter.numPoints());
+}
+
+
 } // namespace tut

commit 52cf6c4d8a5db5db3d0aba3e7358c5f8d98e337a
Author: Daniel Baston <dbaston at gmail.com>
Date:   Thu Jun 6 12:45:59 2019 -0400

    Use custom GeometryComponentFilter in PreparedPolygonPredicate to avoid creating vectors

diff --git a/src/geom/prep/PreparedPolygonPredicate.cpp b/src/geom/prep/PreparedPolygonPredicate.cpp
index 5a1c7c3..261312e 100644
--- a/src/geom/prep/PreparedPolygonPredicate.cpp
+++ b/src/geom/prep/PreparedPolygonPredicate.cpp
@@ -20,6 +20,7 @@
 #include <geos/geom/prep/PreparedPolygonPredicate.h>
 #include <geos/geom/prep/PreparedPolygon.h>
 #include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateFilter.h>
 #include <geos/geom/util/ComponentCoordinateExtracter.h>
 #include <geos/geom/Location.h>
 #include <geos/algorithm/locate/PointOnGeometryLocator.h>
@@ -37,76 +38,81 @@ namespace prep { // geos.geom.prep
 //
 // protected:
 //
+struct AnyMatchingLocationFilter : public GeometryComponentFilter {
+    explicit AnyMatchingLocationFilter(algorithm::locate::PointOnGeometryLocator* locator, int loc) :
+        pt_locator(locator), test_loc(loc), found(false) {}
+
+    algorithm::locate::PointOnGeometryLocator* pt_locator;
+    const int test_loc;
+    bool found;
+
+    void filter_ro(const Geometry* g) override {
+        auto pt = g->getCoordinate();
+        if (!found) {
+            const int loc = pt_locator->locate(pt);
+            if (loc == test_loc) {
+                found = true;
+            }
+        }
+    }
+};
+
+struct AnyNotMatchingLocationFilter : public GeometryComponentFilter {
+    explicit AnyNotMatchingLocationFilter(algorithm::locate::PointOnGeometryLocator* locator, int loc) :
+            pt_locator(locator), test_loc(loc), found(false) {}
+
+    algorithm::locate::PointOnGeometryLocator* pt_locator;
+    const int test_loc;
+    bool found;
+
+    void filter_ro(const Geometry* g) override {
+        auto pt = g->getCoordinate();
+        if (!found) {
+            const int loc = pt_locator->locate(pt);
+            if (loc != test_loc) {
+                found = true;
+            }
+        }
+    }
+};
+
 bool
 PreparedPolygonPredicate::isAllTestComponentsInTarget(const geom::Geometry* testGeom) const
 {
-    geom::Coordinate::ConstVect pts;
-    geom::util::ComponentCoordinateExtracter::getCoordinates(*testGeom, pts);
-
-    for(std::size_t i = 0, ni = pts.size(); i < ni; i++) {
-        const geom::Coordinate* pt = pts[i];
-        const int loc = prepPoly->getPointLocator()->locate(pt);
-        if(geom::Location::EXTERIOR == loc) {
-            return false;
-        }
-    }
-    return true;
+    AnyMatchingLocationFilter filter(prepPoly->getPointLocator(), geom::Location::EXTERIOR);
+    testGeom->apply_ro(&filter);
+
+    return !filter.found;
 }
 
 bool
 PreparedPolygonPredicate::isAllTestComponentsInTargetInterior(
     const geom::Geometry* testGeom) const
 {
-    geom::Coordinate::ConstVect pts;
-    geom::util::ComponentCoordinateExtracter::getCoordinates(*testGeom, pts);
-
-    for(std::size_t i = 0, ni = pts.size(); i < ni; i++) {
-        const geom::Coordinate* pt = pts[i];
-        const int loc = prepPoly->getPointLocator()->locate(pt);
-        if(geom::Location::INTERIOR != loc) {
-            return false;
-        }
-    }
-    return true;
+    AnyNotMatchingLocationFilter filter(prepPoly->getPointLocator(), geom::Location::INTERIOR);
+    testGeom->apply_ro(&filter);
+
+    return !filter.found;
 }
 
 bool
 PreparedPolygonPredicate::isAnyTestComponentInTarget(
     const geom::Geometry* testGeom) const
 {
-    geom::Coordinate::ConstVect pts;
-    geom::util::ComponentCoordinateExtracter::getCoordinates(*testGeom, pts);
+    AnyNotMatchingLocationFilter filter(prepPoly->getPointLocator(), geom::Location::EXTERIOR);
+    testGeom->apply_ro(&filter);
 
-    for(std::size_t i = 0, ni = pts.size(); i < ni; i++) {
-        const Coordinate* pt = pts[i];
-        const int loc = prepPoly->getPointLocator()->locate(pt);
-        if(geom::Location::EXTERIOR != loc) {
-            return true;
-        }
-    }
-    return false;
+    return filter.found;
 }
 
 bool
 PreparedPolygonPredicate::isAnyTestComponentInTargetInterior(
     const geom::Geometry* testGeom) const
 {
-    if (testGeom->getGeometryTypeId() == GEOS_POINT) {
-        // Avoid creating a vector to store a single point
-        return prepPoly->getPointLocator()->locate(testGeom->getCoordinate()) == geom::Location::INTERIOR;
-    }
+    AnyMatchingLocationFilter filter(prepPoly->getPointLocator(), geom::Location::INTERIOR);
+    testGeom->apply_ro(&filter);
 
-    geom::Coordinate::ConstVect pts;
-    geom::util::ComponentCoordinateExtracter::getCoordinates(*testGeom, pts);
-
-    for(std::size_t i = 0, ni = pts.size(); i < ni; i++) {
-        const Coordinate* pt = pts[i];
-        const int loc = prepPoly->getPointLocator()->locate(pt);
-        if(geom::Location::INTERIOR == loc) {
-            return true;
-        }
-    }
-    return false;
+    return filter.found;
 }
 
 bool

commit c8735c3674b5613890c51be8180c193a61a73c81
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Jun 5 21:52:25 2019 -0400

    Add Point short-circuit to PreparedPolygonPredicate::isAnyTestComponentInTargetInterior

diff --git a/src/geom/prep/PreparedPolygonPredicate.cpp b/src/geom/prep/PreparedPolygonPredicate.cpp
index faff645..5a1c7c3 100644
--- a/src/geom/prep/PreparedPolygonPredicate.cpp
+++ b/src/geom/prep/PreparedPolygonPredicate.cpp
@@ -91,6 +91,11 @@ bool
 PreparedPolygonPredicate::isAnyTestComponentInTargetInterior(
     const geom::Geometry* testGeom) const
 {
+    if (testGeom->getGeometryTypeId() == GEOS_POINT) {
+        // Avoid creating a vector to store a single point
+        return prepPoly->getPointLocator()->locate(testGeom->getCoordinate()) == geom::Location::INTERIOR;
+    }
+
     geom::Coordinate::ConstVect pts;
     geom::util::ComponentCoordinateExtracter::getCoordinates(*testGeom, pts);
 

commit 4010bac2d20d87a4b5888fb0f0a30c084e5c6a6c
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Jun 5 21:47:48 2019 -0400

    Avoid performing envelope test against Points
    
    This causes an Envelope to be created and allocated for each point.

diff --git a/src/geom/prep/BasicPreparedGeometry.cpp b/src/geom/prep/BasicPreparedGeometry.cpp
index f3d62c6..436b316 100644
--- a/src/geom/prep/BasicPreparedGeometry.cpp
+++ b/src/geom/prep/BasicPreparedGeometry.cpp
@@ -40,12 +40,20 @@ BasicPreparedGeometry::setGeometry(const geom::Geometry* geom)
 bool
 BasicPreparedGeometry::envelopesIntersect(const geom::Geometry* g) const
 {
+    if (g->getGeometryTypeId() == GEOS_POINT) {
+        return baseGeom->getEnvelopeInternal()->intersects(*(g->getCoordinate()));
+    }
+
     return baseGeom->getEnvelopeInternal()->intersects(g->getEnvelopeInternal());
 }
 
 bool
 BasicPreparedGeometry::envelopeCovers(const geom::Geometry* g) const
 {
+    if (g->getGeometryTypeId() == GEOS_POINT) {
+        return baseGeom->getEnvelopeInternal()->covers(g->getCoordinate());
+    }
+
     return baseGeom->getEnvelopeInternal()->covers(g->getEnvelopeInternal());
 }
 

commit 9d85434cb937f2e837e2c10cbee44817a4cd81e6
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Jun 5 21:33:24 2019 -0400

    Avoid double-testing all positive PIP results in
    AbstractPreparedPolygonContains

diff --git a/src/geom/prep/AbstractPreparedPolygonContains.cpp b/src/geom/prep/AbstractPreparedPolygonContains.cpp
index 49bfe6f..4cc8f58 100644
--- a/src/geom/prep/AbstractPreparedPolygonContains.cpp
+++ b/src/geom/prep/AbstractPreparedPolygonContains.cpp
@@ -112,9 +112,11 @@ AbstractPreparedPolygonContains::eval(const geom::Geometry* geom)
     //
     // If a point of any test components does not lie in target,
     // result is false
-    bool isAllInTargetArea = isAllTestComponentsInTarget(geom);
-    if(!isAllInTargetArea) {
-        return false;
+    if (!requireSomePointInInterior || geom->getGeometryTypeId() != GEOS_POINT) {
+        bool isAllInTargetArea = isAllTestComponentsInTarget(geom);
+        if (!isAllInTargetArea) {
+            return false;
+        }
     }
 
     // If the test geometry consists of only Points,

commit 64f3ec8663bd210144fe2fc563688e7cb0f42f94
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Jun 5 21:22:21 2019 -0400

    Use adaptive OrientationIndex in RayCrossingCounter
    
    Provides a roughly 20% gain in GEOSPreparedContains benchmark.

diff --git a/include/geos/algorithm/RayCrossingCounter.h b/include/geos/algorithm/RayCrossingCounter.h
index fe5dc88..fbbebcc 100644
--- a/include/geos/algorithm/RayCrossingCounter.h
+++ b/include/geos/algorithm/RayCrossingCounter.h
@@ -92,22 +92,6 @@ public:
     static int locatePointInRing(const geom::Coordinate& p,
                                  const std::vector<const geom::Coordinate*>& ring);
 
-    /** \brief
-     * Returns the index of the direction of the point <code>q</code>
-     * relative to a vector specified by <code>p1-p2</code>.
-     *
-     * @param p1 the origin point of the vector
-     * @param p2 the final point of the vector
-     * @param q the point to compute the direction to
-     *
-     * @return 1 if q is counter-clockwise (left) from p1-p2
-     * @return -1 if q is clockwise (right) from p1-p2
-     * @return 0 if q is collinear with p1-p2
-     */
-    static int orientationIndex(const geom::Coordinate& p1,
-                                const geom::Coordinate& p2,
-                                const geom::Coordinate& q);
-
     RayCrossingCounter(const geom::Coordinate& p_point)
         :	point(p_point),
           crossingCount(0),
diff --git a/src/algorithm/RayCrossingCounter.cpp b/src/algorithm/RayCrossingCounter.cpp
index 6593bb0..a4ae339 100644
--- a/src/algorithm/RayCrossingCounter.cpp
+++ b/src/algorithm/RayCrossingCounter.cpp
@@ -75,21 +75,6 @@ RayCrossingCounter::locatePointInRing(const geom::Coordinate& point,
     return rcc.getLocation();
 }
 
-/*public static*/
-int
-RayCrossingCounter::orientationIndex(const geom::Coordinate& p1,
-                                     const geom::Coordinate& p2, const geom::Coordinate& q)
-{
-    // travelling along p1->p2, turn counter clockwise to get to q return 1,
-    // travelling along p1->p2, turn clockwise to get to q return -1,
-    // p1, p2 and q are colinear return 0.
-    double dx1 = p2.x - p1.x;
-    double dy1 = p2.y - p1.y;
-    double dx2 = q.x - p2.x;
-    double dy2 = q.y - p2.y;
-    return CGAlgorithmsDD::signOfDet2x2(dx1, dy1, dx2, dy2);
-}
-
 void
 RayCrossingCounter::countSegment(const geom::Coordinate& p1,
                                  const geom::Coordinate& p2)
@@ -138,7 +123,7 @@ RayCrossingCounter::countSegment(const geom::Coordinate& p1,
             ((p2.y > point.y) && (p1.y <= point.y))) {
         // For an upward edge, orientationIndex will be positive when p1->p2
         // crosses ray. Conversely, downward edges should have negative sign.
-        int sign = orientationIndex(p1, p2, point);
+        int sign = CGAlgorithmsDD::orientationIndex(p1, p2, point);
         if(sign == 0) {
             isPointOnSegment = true;
             return;

commit 4d61fb5d4739fdb84975792d29465a009bca0550
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Jun 5 21:20:54 2019 -0400

    Add GEOSPreparedContains perf test

diff --git a/benchmarks/capi/CMakeLists.txt b/benchmarks/capi/CMakeLists.txt
index 7a5d849..4fdf0e3 100644
--- a/benchmarks/capi/CMakeLists.txt
+++ b/benchmarks/capi/CMakeLists.txt
@@ -16,3 +16,6 @@ target_include_directories(perf_memleak_mp_prep
     $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
     $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>)
 target_link_libraries(perf_memleak_mp_prep PRIVATE geos_c)
+
+add_executable(perf_geospreparedcontains GEOSPreparedContainsPerfTest.cpp)
+target_link_libraries(perf_geospreparedcontains PRIVATE geos geos_c)
\ No newline at end of file
diff --git a/benchmarks/capi/GEOSPreparedContainsPerfTest.cpp b/benchmarks/capi/GEOSPreparedContainsPerfTest.cpp
new file mode 100644
index 0000000..c0c9ccd
--- /dev/null
+++ b/benchmarks/capi/GEOSPreparedContainsPerfTest.cpp
@@ -0,0 +1,114 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2019 Daniel Baston <dbaston at gmail.com>
+ *
+ * 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/profiler.h>
+#include <geos_c.h>
+
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/Polygon.h>
+#include <geos/geom/util/SineStarFactory.h>
+
+#include <algorithm>
+#include <random>
+#include <vector>
+#include <memory>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+class GEOSPreparedContainsPerfTest {
+
+public:
+    void test(const GEOSGeometry* g, size_t num_points) {
+        using namespace geos::geom;
+
+        double xmin, xmax, ymin, ymax;
+
+        GEOSGeom_getXMin(g, &xmin);
+        GEOSGeom_getXMax(g, &xmax);
+        GEOSGeom_getYMin(g, &ymin);
+        GEOSGeom_getYMax(g, &ymax);
+
+        std::default_random_engine e(12345);
+        std::uniform_real_distribution<> xdist(xmin, xmax);
+        std::uniform_real_distribution<> ydist(ymin, ymax);
+
+        std::vector<Coordinate> coords(num_points);
+        std::generate(coords.begin(), coords.end(), [&xdist, &ydist, &e]() {
+            return Coordinate(xdist(e), ydist(e));
+        });
+
+        geos::util::Profile sw("GEOSPreparedContains");
+        sw.start();
+
+        size_t hits = 0;
+        auto prep = GEOSPrepare(g);
+        for (const auto& c : coords) {
+            auto seq = GEOSCoordSeq_create(1, 2);
+            GEOSCoordSeq_setX(seq, 0, c.x);
+            GEOSCoordSeq_setY(seq, 0, c.y);
+            auto pt = GEOSGeom_createPoint(seq);
+
+            if (GEOSPreparedContains(prep, pt)) {
+                hits++;
+            }
+
+            GEOSGeom_destroy(pt);
+        }
+
+        GEOSPreparedGeom_destroy(prep);
+
+        sw.stop();
+
+        std::cout << sw.name << ": " << hits << " hits from " << num_points << " points in " <<  sw.getTotFormatted() << std::endl;
+
+    }
+};
+
+int main(int argc, char** argv) {
+    if (argc != 3) {
+        std::cout << "perf_geospreparedcontins performs a specified number of point-in-polygon tests" << std::endl;
+        std::cout << "on randomly generated points from the bounding box of a single geometry provided" << std::endl;
+        std::cout << "in a file as WKT." << std::endl;
+        std::cout << std::endl;
+        std::cout << "Usage: perf_geospreparedcontins [wktfile] [n]" << std::endl;
+        return 0;
+    }
+
+    GEOSPreparedContainsPerfTest tester;
+
+    int n = std::atoi(argv[2]);
+    std::cout << "Performing " << n << " point-in-polygon tests." << std::endl;
+
+    std::string fname{argv[1]};
+    std::cout << "Reading shape from " << fname << std::endl;
+
+    std::ifstream f(fname);
+    std::stringstream buff;
+    buff << f.rdbuf();
+    f.close();
+
+    std::string wkt = buff.str();
+    buff.clear();
+
+    initGEOS(nullptr, nullptr);
+    GEOSGeometry* g = GEOSGeomFromWKT(wkt.c_str());
+    wkt.clear();
+
+    tester.test(g, n);
+
+    GEOSGeom_destroy(g);
+}

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

Summary of changes:
 benchmarks/capi/CMakeLists.txt                     |   3 +
 benchmarks/capi/GEOSPreparedContainsPerfTest.cpp   | 114 ++++++++++++++++++
 include/geos/algorithm/RayCrossingCounter.h        |  16 ---
 include/geos/geom/GeometryComponentFilter.h        |   2 +
 .../geom/prep/AbstractPreparedPolygonContains.h    |  10 ++
 include/geos/geom/prep/PreparedPolygonPredicate.h  |  12 +-
 src/algorithm/RayCrossingCounter.cpp               |  17 +--
 src/geom/GeometryCollection.cpp                    |   4 +-
 src/geom/Polygon.cpp                               |   4 +-
 src/geom/prep/AbstractPreparedPolygonContains.cpp  |  55 ++++++---
 src/geom/prep/BasicPreparedGeometry.cpp            |   8 ++
 src/geom/prep/PreparedPolygonPredicate.cpp         | 134 ++++++++++++++-------
 tests/unit/geom/GeometryComponentFilterTest.cpp    |  88 ++++++++++++++
 .../tests/general/TestPreparedPolygonPredicate.xml |  86 ++++++++++++-
 14 files changed, 451 insertions(+), 102 deletions(-)
 create mode 100644 benchmarks/capi/GEOSPreparedContainsPerfTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list