[Git][debian-gis-team/geos][upstream] New upstream version 3.12.0~beta2

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Thu Jun 15 04:16:25 BST 2023



Bas Couwenberg pushed to branch upstream at Debian GIS Project / geos


Commits:
a8e816e4 by Bas Couwenberg at 2023-06-14T22:03:52+02:00
New upstream version 3.12.0~beta2
- - - - -


16 changed files:

- NEWS.md
- Version.txt
- capi/geos_c.h.in
- capi/geos_ts_c.cpp
- examples/README.md
- + examples/capi_indexed_predicate.c
- examples/capi_strtree.c
- include/geos/coverage/CoverageRingEdges.h
- include/geos/geom/Geometry.h
- include/geos/geom/HeuristicOverlay.h
- release.md
- src/coverage/CoverageBoundarySegmentFinder.cpp
- src/geom/Geometry.cpp
- src/geom/HeuristicOverlay.cpp
- tests/unit/geom/DimensionTest.cpp
- + tests/unit/geom/HeuristicOverlayTest.cpp


Changes:

=====================================
NEWS.md
=====================================
@@ -74,6 +74,7 @@ xxxx-xx-xx
   - Reduce artifacts in single-sided Buffers: (GH-665 #810 and #712, Sandro Santilli)
   - GeoJSONReader: Fix 2D empty geometry creation (GH-909, Mike Taves)
   - GEOSClipByRect: Fix case with POINT EMPTY (GH-913, Mike Taves)
+  - Support mixed GeometryCollection in overlay ops (GH-797, Paul Ramsey)
 
 - Changes:
   - Remove Orientation.isCCW exception to simplify logic and align with JTS (GH-878, Martin Davis)


=====================================
Version.txt
=====================================
@@ -5,7 +5,7 @@ GEOS_VERSION_MINOR=12
 GEOS_VERSION_PATCH=0
 
 # OPTIONS: "", "dev", "rc1" etc.
-GEOS_PATCH_WORD=beta1
+GEOS_PATCH_WORD=beta2
 
 # GEOS CAPI Versions
 #


=====================================
capi/geos_c.h.in
=====================================
@@ -263,7 +263,7 @@ typedef void (*GEOSQueryCallback)(void *item, void *userdata);
 * \param distance the distance between the items here
 * \param userdata extra data for the calculation
 *
-* \return zero if distance calculation succeeded, non-zero otherwise
+* \return 1 if distance calculation succeeds, 0 otherwise
 *
 * \see GEOSSTRtree_nearest_generic
 * \see GEOSSTRtree_iterate


=====================================
capi/geos_ts_c.cpp
=====================================
@@ -391,7 +391,7 @@ inline auto execute(
                                   decltype(std::declval<F>()())>::type errval,
         F&& f) -> decltype(errval) {
     if (extHandle == nullptr) {
-        throw std::runtime_error("GEOS context handle is unintialized, call initGEOS");
+        throw std::runtime_error("GEOS context handle is uninitialized, call initGEOS");
     }
 
     GEOSContextHandleInternal_t* handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
@@ -415,7 +415,7 @@ inline auto execute(
 template<typename F, typename std::enable_if<!std::is_void<decltype(std::declval<F>()())>::value, std::nullptr_t>::type = nullptr>
 inline auto execute(GEOSContextHandle_t extHandle, F&& f) -> decltype(f()) {
     if (extHandle == nullptr) {
-        throw std::runtime_error("context handle is unintialized, call initGEOS");
+        throw std::runtime_error("context handle is uninitialized, call initGEOS");
     }
 
     GEOSContextHandleInternal_t* handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);


=====================================
examples/README.md
=====================================
@@ -4,6 +4,7 @@
 * `capi_read_ts` uses the "re-entrant" C API (threadsafe) to read two WKT geometries, calculate the intersection and print the result
 * `capi_prepared` uses the standard C API to read one WKT geometry, and fill it with a point grid, applying a high performance "prepared" geometry to speed up intersection testing
 * `capi_strtree` uses the standard C API to build a random collection of points, and then search that collection quickly to find the nearest to a query point
+* `capi_indexed_predicate` uses the standard C API API to build an STRtree index on a custom class, and then query that index with a prepared geometry, returning a list of matching items
 * `cpp_read` uses the C++ API to read two WKT geometries, calculate the intersection and print the result
 * `cpp_strtree` uses the C++ API to build an STRtree index on a custom class, and then query that index
 


=====================================
examples/capi_indexed_predicate.c
=====================================
@@ -0,0 +1,219 @@
+/*
+* # GEOS C API example 4
+*
+* Build a spatial index and search it for all points
+* completely contained in arbitrary query polygon.
+*
+* cc -I/usr/local/include capi_indexed_predicate.c -o capi_indexed_predicate -L/usr/local/lib -lgeos_c
+*/
+
+/* System headers */
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+
+/* Only the CAPI header is required */
+#include <geos_c.h>
+
+/*
+* GEOS requires two message handlers to return
+* error and notice message to the calling program.
+*
+*   typedef void(* GEOSMessageHandler) (const char *fmt,...)
+*
+* Here we stub out an example that just prints the
+* messages to stdout.
+*/
+static void
+geos_message_handler(const char* fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    vprintf (fmt, ap);
+    va_end(ap);
+}
+
+/*
+* An application will want to index items, which have
+* some attributes and a geometry part.
+*/
+typedef struct
+{
+    GEOSGeometry* geom;
+    size_t id;
+} item_t;
+
+/*
+* A user data struct to pass to the index callback function
+*/
+typedef struct
+{
+    const GEOSPreparedGeometry* prepgeom;
+    item_t** items;
+    size_t  nItems;
+    size_t  szItems;
+} userdata_t;
+
+/*
+* Userdata both holds our output list of found items and
+* our input PreparedGeometry for fast spatial tests.
+*/
+userdata_t *
+userdata_init(GEOSGeometry* geom)
+{
+    userdata_t* ud = malloc(sizeof(userdata_t));
+    ud->prepgeom = GEOSPrepare(geom);
+    ud->nItems = 0;
+    ud->szItems = 16;
+    ud->items = malloc(sizeof(item_t*) * ud->szItems);
+    return ud;
+}
+
+/*
+* Free the items list and the PreparedGeometry
+*/
+void
+userdata_free(userdata_t* ud)
+{
+    GEOSPreparedGeom_destroy(ud->prepgeom);
+    free(ud->items);
+    free(ud);
+}
+
+
+/*
+* Generate a random item with a location in the range of
+* POINT(0..range, 0..range). Caller must free.
+*/
+static item_t *
+item_random(double range)
+{
+    item_t* item = malloc(sizeof(item_t));
+    double x = range * rand() / RAND_MAX;
+    double y = range * rand() / RAND_MAX;
+    /* Make a point in the point grid */
+    item->geom = GEOSGeom_createPointFromXY(x, y);
+    item->id = rand();
+    return item;
+}
+
+/*
+* Free an item and its geometry.
+*/
+void
+item_free(item_t* item)
+{
+    if (item && item->geom) GEOSGeom_destroy(item->geom);
+    if (item) free(item);
+}
+
+/*
+* Utility function to write out contents of item
+*/
+void
+item_print(const item_t* item)
+{
+    double x, y;
+    GEOSGeomGetX(item->geom, &x);
+    GEOSGeomGetY(item->geom, &y);
+    printf("item %10zu (%g, %g)\n", item->id, x, y);
+}
+
+/*
+* Item query callback for GEOSSTRtree_query()
+*/
+void
+itemQueryCallback(void* item, void* userdata)
+{
+    userdata_t* ud = (userdata_t*)userdata;
+    item_t* indexitem = (item_t*)item;
+    if (GEOSPreparedIntersects(ud->prepgeom, indexitem->geom)) {
+        if (ud->nItems == ud->szItems) {
+            ud->szItems *= 2;
+            ud->items = realloc(ud->items, sizeof(item_t*) * ud->szItems);
+        }
+        ud->items[ud->nItems++] = indexitem;
+    }
+    return;
+}
+
+
+int main()
+{
+    /* Send notice and error messages to our stdout handler */
+    initGEOS(geos_message_handler, geos_message_handler);
+
+    /* How many random items to add to our index */
+    const size_t nItems = 10000;
+    /* The coordinate range of the random locations (0->100.0) */
+    const double range = 100.0;
+    /* Set the seed for rand() */
+    srand(time(NULL));
+
+    /*
+    * The tree doesn't take ownership of inputs, it just
+    * holds pointers, so we keep a list of allocated items
+    * handy in an array for future clean-up
+    */
+    item_t* items[nItems];
+    /*
+    * The create parameter for the tree is not the
+    * number of inputs, it is the number of entries
+    * per node. 10 is a good default number to use.
+    */
+    GEOSSTRtree* tree = GEOSSTRtree_create(10);
+    for (size_t i = 0; i < nItems; i++) {
+        /* Make a random point */
+        item_t* item = item_random(range);
+        /* Store away a reference so we can free it after */
+        items[i] = item;
+        /* Add an entry for it to the tree */
+        GEOSSTRtree_insert(tree, item->geom, item);
+    }
+
+    /* Prepare to read geometries in as text */
+    GEOSWKTReader* reader = GEOSWKTReader_create();
+
+    /* Set up a query rectangle for index query */
+    const char* wkt_bounds = "POLYGON((20 20, 20 24, 24 24, 24 23, 21 23, 21 21, 24 21, 24 20, 20 20))";
+    GEOSGeometry* geom_query = GEOSWKTReader_read(reader, wkt_bounds);
+
+    /* Set up the prepared geometry for the exact tests */
+    userdata_t* ud = userdata_init(geom_query);
+
+    /* Find all items that touch the bounds */
+    /* For non-rectangular query geometry, this will be an over-determined set */
+    GEOSSTRtree_query(
+        tree,              // STRTree to query
+        geom_query,        // GEOSGeometry query bounds
+        itemQueryCallback, // Callback to process index entries that pass query
+        ud);             // Userdata to hand to the callback
+
+    /* Print out the items we found */
+    printf("Found %zu items in the polygon: %s\n", ud->nItems, wkt_bounds);
+    for (size_t i = 0; i < ud->nItems; i++) {
+        item_print(ud->items[i]);
+    }
+
+    /* Done with the found items and prepared geometry now */
+    userdata_free(ud);
+
+    /* Free the query bounds geometry */
+    GEOSGeom_destroy(geom_query);
+
+    /* Freeing the tree does not free the tree inputs */
+    GEOSSTRtree_destroy(tree);
+
+    /* Free all the items in our random item list */
+    for (size_t i = 0; i < nItems; i++) {
+        item_free(items[i]);
+    }
+
+    /* Clean up the global context */
+    finishGEOS();
+
+    /* Done */
+    return 0;
+}


=====================================
examples/capi_strtree.c
=====================================
@@ -2,13 +2,17 @@
 * # GEOS C API example 3
 *
 * Build a spatial index and search it for a
-* nearest pair.
+* nearest neighbor and for a query bounds.
+*
+* cc -I/usr/local/include capi_strtree.c -o capi_strtree -L/usr/local/lib -lgeos_c
 */
 
-/* To print to stdout */
+/* System headers */
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#include <math.h>
+#include <time.h>
 
 /* Only the CAPI header is required */
 #include <geos_c.h>
@@ -32,17 +36,63 @@ geos_message_handler(const char* fmt, ...)
 }
 
 /*
-* Generate a random point in the range of
-* POINT(0..range, 0..range). Caller must
-* free.
+* An application will want to index items, which have
+* some attributes and a geometry part.
+*/
+typedef struct
+{
+    GEOSGeometry* geom;
+    size_t id;
+} item_t;
+
+/*
+* Generate a random item with a location in the range of
+* POINT(0..range, 0..range). Caller must free.
 */
-static GEOSGeometry*
-geos_random_point(double range)
+static item_t *
+random_item(double range)
 {
+    item_t* item = malloc(sizeof(item_t));
     double x = range * rand() / RAND_MAX;
     double y = range * rand() / RAND_MAX;
     /* Make a point in the point grid */
-    return GEOSGeom_createPointFromXY(x, y);
+    item->geom = GEOSGeom_createPointFromXY(x, y);
+    item->id = rand();
+    return item;
+}
+
+/*
+* Free an item and its geometry.
+*/
+void
+free_item(item_t* item)
+{
+    if (item && item->geom) GEOSGeom_destroy(item->geom);
+    if (item) free(item);
+}
+
+/*
+* Item distance callback for GEOSSTRtree_nearest_generic()
+*/
+int
+itemDistanceCallback(const void* item1, const void* item2, double* distance, void* userdata)
+{
+    item_t* obj1 = (item_t*)item1;
+    item_t* obj2 = (item_t*)item2;
+    return GEOSDistance(obj1->geom, obj2->geom, distance);
+}
+
+/*
+* Item query callback for GEOSSTRtree_query()
+*/
+void
+itemQueryCallback(void* item, void* userdata)
+{
+    double x, y;
+    item_t* i = (item_t*)item;
+    GEOSGeomGetX(i->geom, &x);
+    GEOSGeomGetY(i->geom, &y);
+    printf("Found item %10zu at (%g, %g)\n", i->id, x, y);
 }
 
 
@@ -51,65 +101,94 @@ int main()
     /* Send notice and error messages to our stdout handler */
     initGEOS(geos_message_handler, geos_message_handler);
 
-    /* How many points to add to our random field */
-    const size_t npoints = 10000;
-    /* The coordinate range of the field (0->100.0) */
+    /* How many random items to add to our index */
+    const size_t nItems = 10000;
+    /* The coordinate range of the random locations (0->100.0) */
     const double range = 100.0;
+    /* Set the seed for rand() */
+    srand(time(NULL));
 
     /*
-    * The tree doesn't take ownership of inputs just
-    * holds references, so we keep our point field
-    * handy in an array
+    * The tree doesn't take ownership of inputs, it just
+    * holds pointers, so we keep a list of allocated items
+    * handy in an array for future clean-up
     */
-    GEOSGeometry* geoms[npoints];
+    item_t* items[nItems];
     /*
     * The create parameter for the tree is not the
     * number of inputs, it is the number of entries
     * per node. 10 is a good default number to use.
     */
     GEOSSTRtree* tree = GEOSSTRtree_create(10);
-    for (size_t i = 0; i < npoints; i++) {
+    for (size_t i = 0; i < nItems; i++) {
         /* Make a random point */
-        GEOSGeometry* geom = geos_random_point(range);
+        item_t* item = random_item(range);
         /* Store away a reference so we can free it after */
-        geoms[i] = geom;
+        items[i] = item;
         /* Add an entry for it to the tree */
-        GEOSSTRtree_insert(tree, geom, geom);
+        GEOSSTRtree_insert(tree, item->geom, item);
     }
 
-    /* Random point to compare to the field */
-    GEOSGeometry* geom_random = geos_random_point(range);
-    /* Nearest point in the field to our test point */
-    const GEOSGeometry* geom_nearest = GEOSSTRtree_nearest(tree, geom_random);
-
-    /* Convert results to WKT */
+    /* Prepare to write some geometries out as text */
     GEOSWKTWriter* writer = GEOSWKTWriter_create();
     /* Trim trailing zeros off output */
     GEOSWKTWriter_setTrim(writer, 1);
     GEOSWKTWriter_setRoundingPrecision(writer, 3);
-    char* wkt_random = GEOSWKTWriter_write(writer, geom_random);
-    char* wkt_nearest = GEOSWKTWriter_write(writer, geom_nearest);
-    GEOSWKTWriter_destroy(writer);
 
-    /* Print answer */
+    /* Prepare to read some geometries in as text */
+    GEOSWKTReader* reader = GEOSWKTReader_create();
+
+    /* Random item to query the index with */
+    item_t* item_random = random_item(range);
+    /* Nearest item in the index to our random item */
+    const item_t* item_nearest = GEOSSTRtree_nearest_generic(
+        tree,                 // STRTree to query
+        item_random,          // Item to use in search
+        item_random->geom,    // Geometry to seed search
+        itemDistanceCallback, // Callback to process nearest object
+        NULL);                // Userdata to hand to the callback
+
+    /* Convert geometry to WKT */
+    char* wkt_random  = GEOSWKTWriter_write(writer, item_random->geom);
+    char* wkt_nearest = GEOSWKTWriter_write(writer, item_nearest->geom);
+
+    /* Print random query point and nearest point */
     printf(" Random Point: %s\n", wkt_random);
     printf("Nearest Point: %s\n", wkt_nearest);
 
-    /* Clean up all allocated objects */
-    /* Destroying tree does not destroy inputs */
-    GEOSSTRtree_destroy(tree);
-    GEOSGeom_destroy(geom_random);
-    /* Destroy all the points in our random field */
-    for (size_t i = 0; i < npoints; i++) {
-        GEOSGeom_destroy(geoms[i]);
-    }
-    /*
-    * Don't forget to free memory allocated by the
-    * printing functions!
-    */
+    /* Don't forget to free memory allocated for WKT! */
     GEOSFree(wkt_random);
     GEOSFree(wkt_nearest);
 
+    /* Set up a query rectangle for index query */
+    const char* wkt_bounds = "POLYGON((20 20, 22 20, 22 22, 20 22, 20 20))";
+    GEOSGeometry* geom_query = GEOSWKTReader_read(reader, wkt_bounds);
+
+    /* Find all items that touch the bounds */
+    /* For non-rectangular query geometry, this will be an over-determined set */
+    GEOSSTRtree_query(
+        tree,              // STRTree to query
+        geom_query,        // GEOSGeometry query bounds
+        itemQueryCallback, // Callback to process index entries that pass query
+        NULL);             // Userdata to hand to the callback
+
+    /* Free the query bounds geometry */
+    GEOSGeom_destroy(geom_query);
+
+    /* Free the WKT writer and reader */
+    GEOSWKTWriter_destroy(writer);
+    GEOSWKTReader_destroy(reader);
+
+    /* Freeing the tree does not free the tree inputs */
+    GEOSSTRtree_destroy(tree);
+
+    /* Free all the items in our random item list */
+    for (size_t i = 0; i < nItems; i++) {
+        free_item(items[i]);
+    }
+    /* Free our working random item */
+    free_item(item_random);
+
     /* Clean up the global context */
     finishGEOS();
 


=====================================
include/geos/coverage/CoverageRingEdges.h
=====================================
@@ -72,16 +72,6 @@ private:
 
 public:
 
-    /**
-    * Create a new instance for a given coverage.
-    *
-    * @param coverage the set of polygonal geometries in the coverage
-    * @return the edges of the coverage
-    */
-    // static std::unique_ptr<CoverageRingEdges> create(
-    //     std::vector<const Geometry*>& coverage);
-
-
     CoverageRingEdges(std::vector<const Geometry*>& coverage)
         : m_coverage(coverage)
     {


=====================================
include/geos/geom/Geometry.h
=====================================
@@ -364,6 +364,9 @@ public:
         return isDimensionStrict(Dimension::A);
     }
 
+    bool isMixedDimension() const;
+    bool isMixedDimension(Dimension::DimensionType* baseDim) const;
+
     bool isCollection() const {
         int t = getGeometryTypeId();
         return t == GEOS_GEOMETRYCOLLECTION ||


=====================================
include/geos/geom/HeuristicOverlay.h
=====================================
@@ -20,16 +20,80 @@
 #pragma once
 
 #include <geos/export.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/Dimension.h>
+
+
 #include <memory> // for unique_ptr
+#include <vector>
 
-namespace geos {
-namespace geom { // geos::geom
 
+namespace geos {
+namespace geom {
 class Geometry;
+class GeometryFactory;
+}
+}
+
+
+namespace geos {
+namespace geom { // geos::geom
 
 std::unique_ptr<Geometry> GEOS_DLL
 HeuristicOverlay(const Geometry* g0, const Geometry* g1, int opCode);
 
+class StructuredCollection {
+
+public:
+
+    StructuredCollection(const Geometry* g)
+        : factory(g->getFactory())
+        , pt_union(nullptr)
+        , line_union(nullptr)
+        , poly_union(nullptr)
+    {
+        readCollection(g);
+        unionByDimension();
+    };
+
+    StructuredCollection()
+        : factory(nullptr)
+        , pt_union(nullptr)
+        , line_union(nullptr)
+        , poly_union(nullptr)
+    {};
+
+    void readCollection(const Geometry* g);
+    const Geometry* getPolyUnion()  const { return poly_union.get(); }
+    const Geometry* getLineUnion()  const { return line_union.get(); }
+    const Geometry* getPointUnion() const { return pt_union.get(); }
+
+    std::unique_ptr<Geometry> doUnion(const StructuredCollection& a) const;
+    std::unique_ptr<Geometry> doIntersection(const StructuredCollection& a) const;
+    std::unique_ptr<Geometry> doSymDifference(const StructuredCollection& a) const;
+    std::unique_ptr<Geometry> doDifference(const StructuredCollection& a) const;
+    std::unique_ptr<Geometry> doUnaryUnion() const;
+
+    static void toVector(const Geometry* g, std::vector<const Geometry*>& v);
+    void unionByDimension(void);
+
+
+private:
+
+    const GeometryFactory* factory;
+    std::vector<const Geometry*> pts;
+    std::vector<const Geometry*> lines;
+    std::vector<const Geometry*> polys;
+    std::unique_ptr<Geometry> pt_union;
+    std::unique_ptr<Geometry> line_union;
+    std::unique_ptr<Geometry> poly_union;
+
+};
+
+
+
+
+
 } // namespace geos::geom
 } // namespace geos
 


=====================================
release.md
=====================================
@@ -73,6 +73,7 @@ xxxx-xx-xx
   - Reduce artifacts in single-sided Buffers: (GH-665 #810 and #712, Sandro Santilli)
   - GeoJSONReader: Fix 2D empty geometry creation (GH-909, Mike Taves)
   - GEOSClipByRect: Fix case with POINT EMPTY (GH-913, Mike Taves)
+  - Support mixed GeometryCollection in overlay ops (GH-797, Paul Ramsey)
 
 - Changes:
   - Remove Orientation.isCCW exception to simplify logic and align with JTS (GH-878, Martin Davis)


=====================================
src/coverage/CoverageBoundarySegmentFinder.cpp
=====================================
@@ -1,14 +1,16 @@
-/*
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
  * Copyright (c) 2022 Martin Davis.
  *
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
- * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
- * and the Eclipse Distribution License is available at
+ * 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.
  *
- * http://www.eclipse.org/org/documents/edl-v10.php.
- */
+ **********************************************************************/
 
 #include <geos/coverage/CoverageBoundarySegmentFinder.h>
 #include <geos/geom/CoordinateSequence.h>


=====================================
src/geom/Geometry.cpp
=====================================
@@ -160,6 +160,34 @@ Geometry::getCentroid() const
     return std::unique_ptr<Point>(getFactory()->createPoint(centPt));
 }
 
+/* public */
+bool
+Geometry::isMixedDimension() const
+{
+    Dimension::DimensionType baseDim = Dimension::DONTCARE;
+    return isMixedDimension(&baseDim);
+}
+
+/* public */
+bool
+Geometry::isMixedDimension(Dimension::DimensionType* baseDim) const
+{
+    if (isCollection()) {
+        for (std::size_t i = 0; i < getNumGeometries(); i++) {
+            if (getGeometryN(i)->isMixedDimension(baseDim))
+                return true;
+        }
+        return false;
+    }
+    else {
+        if (*baseDim == Dimension::DONTCARE) {
+            *baseDim = getDimension();
+            return false;
+        }
+        return *baseDim != getDimension();
+    }
+}
+
 /*public*/
 bool
 Geometry::getCentroid(CoordinateXY& ret) const


=====================================
src/geom/HeuristicOverlay.cpp
=====================================
@@ -25,6 +25,14 @@
 
 #include <geos/geom/HeuristicOverlay.h>
 #include <geos/operation/overlayng/OverlayNGRobust.h>
+#include <geos/util/IllegalArgumentException.h>
+#include <geos/geom/Dimension.h>
+#include <geos/geom/MultiPoint.h>
+#include <geos/geom/MultiLineString.h>
+#include <geos/geom/MultiPolygon.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/GeometryCollection.h>
+#include <geos/io/WKTWriter.h>
 
 namespace geos {
 namespace geom { // geos::geom
@@ -37,48 +45,355 @@ HeuristicOverlay(const Geometry* g0, const Geometry* g1, int opCode)
 {
     std::unique_ptr<Geometry> ret;
 
-/**************************************************************************/
-
-/*
-* overlayng::OverlayNGRobust carries out the following steps
-*
-* 1. Perform overlay operation using PrecisionModel(float).
-*    If no exception return result.
-* 2. Perform overlay operation using SnappingNoder(tolerance), starting
-*    with a very very small tolerance and increasing it for 5 iterations.
-*    The SnappingNoder moves only nodes that are within tolerance of
-*    other nodes and lines, leaving all the rest undisturbed, for a very
-*    clean result, if it manages to create one.
-*    If a result is found with no exception, return.
-* 3. Perform overlay operation using a PrecisionModel(scale), which
-*    uses a SnapRoundingNoder. Every vertex will be noded to the snapping
-*    grid, resulting in a modified geometry. The SnapRoundingNoder approach
-*    reliably produces results, assuming valid inputs.
-*
-* Running overlayng::OverlayNGRobust at this stage should guarantee
-* that none of the other heuristics are ever needed.
-*/
-        if (g0 == nullptr && g1 == nullptr) {
-            return std::unique_ptr<Geometry>(nullptr);
+    /*
+    * overlayng::OverlayNGRobust does not currently handle
+    * GeometryCollection (collections of mixed dimension)
+    * so we handle that case here.
+    */
+    if ((g0->isMixedDimension() && !g0->isEmpty()) ||
+        (g1->isMixedDimension() && !g1->isEmpty()))
+    {
+        StructuredCollection s0(g0);
+        StructuredCollection s1(g1);
+        switch (opCode) {
+        case OverlayNG::UNION:
+            return s0.doUnion(s1);
+        case OverlayNG::DIFFERENCE:
+            return s0.doDifference(s1);
+        case OverlayNG::SYMDIFFERENCE:
+            return s0.doSymDifference(s1);
+        case OverlayNG::INTERSECTION:
+            return s0.doIntersection(s1);
         }
-        else if (g0 == nullptr) {
-            // Use a unary union for the one-parameter case, as the pairwise
-            // union with one parameter is very intolerant to invalid
-            // collections and multi-polygons.
-            ret = OverlayNGRobust::Union(g1);
+    }
+
+    /*
+    * overlayng::OverlayNGRobust carries out the following steps
+    *
+    * 1. Perform overlay operation using PrecisionModel(float).
+    *    If no exception return result.
+    * 2. Perform overlay operation using SnappingNoder(tolerance), starting
+    *    with a very very small tolerance and increasing it for 5 iterations.
+    *    The SnappingNoder moves only nodes that are within tolerance of
+    *    other nodes and lines, leaving all the rest undisturbed, for a very
+    *    clean result, if it manages to create one.
+    *    If a result is found with no exception, return.
+    * 3. Perform overlay operation using a PrecisionModel(scale), which
+    *    uses a SnapRoundingNoder. Every vertex will be noded to the snapping
+    *    grid, resulting in a modified geometry. The SnapRoundingNoder approach
+    *    reliably produces results, assuming valid inputs.
+    *
+    * Running overlayng::OverlayNGRobust at this stage should guarantee
+    * that none of the other heuristics are ever needed.
+    */
+    if (g0 == nullptr && g1 == nullptr) {
+        return std::unique_ptr<Geometry>(nullptr);
+    }
+    else if (g0 == nullptr) {
+        // Use a unary union for the one-parameter case, as the pairwise
+        // union with one parameter is very intolerant to invalid
+        // collections and multi-polygons.
+        ret = OverlayNGRobust::Union(g1);
+    }
+    else if (g1 == nullptr) {
+        // Use a unary union for the one-parameter case, as the pairwise
+        // union with one parameter is very intolerant to invalid
+        // collections and multi-polygons.
+        ret = OverlayNGRobust::Union(g0);
+    }
+    else {
+        ret = OverlayNGRobust::Overlay(g0, g1, opCode);
+    }
+
+    return ret;
+}
+
+/* public */
+void
+StructuredCollection::readCollection(const Geometry* g)
+{
+    if (!factory) factory = g->getFactory();
+    if (g->isCollection()) {
+        for (std::size_t i = 0; i < g->getNumGeometries(); i++) {
+            readCollection(g->getGeometryN(i));
         }
-        else if (g1 == nullptr) {
-            // Use a unary union for the one-parameter case, as the pairwise
-            // union with one parameter is very intolerant to invalid
-            // collections and multi-polygons.
-            ret = OverlayNGRobust::Union(g0);
+    }
+    else {
+        if (g->isEmpty()) return;
+        switch (g->getGeometryTypeId()) {
+            case GEOS_POINT:
+                pts.push_back(g);
+                break;
+            case GEOS_LINESTRING:
+                lines.push_back(g);
+                break;
+            case GEOS_POLYGON:
+                polys.push_back(g);
+                break;
+            default:
+                throw util::IllegalArgumentException("cannot process unexpected collection");
+        }
+    }
+}
+
+/* public static */
+void
+StructuredCollection::toVector(const Geometry* g, std::vector<const Geometry*>& v)
+{
+    if (!g || g->isEmpty()) return;
+    if (g->isCollection()) {
+        for (std::size_t i = 0; i < g->getNumGeometries(); i++) {
+            toVector(g->getGeometryN(i), v);
         }
-        else {
-            ret = OverlayNGRobust::Overlay(g0, g1, opCode);
+    }
+    else {
+        switch (g->getGeometryTypeId()) {
+            case GEOS_POINT:
+            case GEOS_LINESTRING:
+            case GEOS_POLYGON:
+                v.push_back(g);
+                break;
+            default:
+                return;
         }
+    }
+}
+
+
+/* public */
+void
+StructuredCollection::unionByDimension(void)
+{
+    /*
+    * Remove duplication within each dimension, so that there
+    * is only one object covering any particular space within
+    * that dimension.
+    * This makes reasoning about the collection-on-collection
+    * operations a little easier later on.
+    */
+    std::unique_ptr<MultiPoint> pt_col = factory->createMultiPoint(pts);
+    std::unique_ptr<MultiLineString> line_col = factory->createMultiLineString(lines);
+    std::unique_ptr<MultiPolygon> poly_col = factory->createMultiPolygon(polys);
+
+    pt_union = OverlayNGRobust::Union(static_cast<const Geometry*>(pt_col.get()));
+    line_union = OverlayNGRobust::Union(static_cast<const Geometry*>(line_col.get()));
+    poly_union = OverlayNGRobust::Union(static_cast<const Geometry*>(poly_col.get()));
+
+    // io::WKTWriter w;
+    // std::cout << "line_col " << w.write(*line_col) << std::endl;
+    // std::cout << "line_union " << w.write(*line_union) << std::endl;
+
+    if (! pt_union->isPuntal())
+        throw util::IllegalArgumentException("union of points not puntal");
+    if (! line_union->isLineal())
+        throw util::IllegalArgumentException("union of lines not lineal");
+    if (! poly_union->isPolygonal())
+        throw util::IllegalArgumentException("union of polygons not polygonal");
+}
+
+/* public */
+std::unique_ptr<Geometry>
+StructuredCollection::doUnaryUnion() const
+{
+    /*
+    * Before output, we clean up the components to remove spatial
+    * duplication. Points that lines pass through. Lines that are covered
+    * by polygonal areas already. Provides a "neater" output that still
+    * covers all the area it should.
+    */
+    std::unique_ptr<Geometry> pts_less_lines = OverlayNGRobust::Overlay(
+        pt_union.get(),
+        line_union.get(),
+        OverlayNG::DIFFERENCE);
+
+    std::unique_ptr<Geometry> pts_less_polys_lines = OverlayNGRobust::Overlay(
+        pts_less_lines.get(),
+        poly_union.get(),
+        OverlayNG::DIFFERENCE);
+
+    std::unique_ptr<Geometry> lines_less_polys = OverlayNGRobust::Overlay(
+        line_union.get(),
+        poly_union.get(),
+        OverlayNG::DIFFERENCE);
+
+    std::vector<const Geometry*> geoms;
+    toVector(pts_less_polys_lines.get(), geoms);
+    toVector(lines_less_polys.get(), geoms);
+    toVector(poly_union.get(), geoms);
+
+    return factory->buildGeometry(geoms.begin(), geoms.end());
+}
+
+
+/* public */
+std::unique_ptr<Geometry>
+StructuredCollection::doUnion(const StructuredCollection& a) const
+{
+
+    auto poly_union_poly = OverlayNGRobust::Overlay(
+        a.getPolyUnion(),
+        poly_union.get(),
+        OverlayNG::UNION);
+
+    auto line_union_line = OverlayNGRobust::Overlay(
+        a.getLineUnion(),
+        line_union.get(),
+        OverlayNG::UNION);
+
+    auto pt_union_pt = OverlayNGRobust::Overlay(
+        a.getPointUnion(),
+        pt_union.get(),
+        OverlayNG::UNION);
+
+    StructuredCollection c;
+    c.readCollection(poly_union_poly.get());
+    c.readCollection(line_union_line.get());
+    c.readCollection(pt_union_pt.get());
+    c.unionByDimension();
+    return c.doUnaryUnion();
+}
+
+
+std::unique_ptr<Geometry>
+StructuredCollection::doIntersection(const StructuredCollection& a) const
+{
+    std::unique_ptr<Geometry> poly_inter_poly = OverlayNGRobust::Overlay(
+        poly_union.get(),
+        a.getPolyUnion(),
+        OverlayNG::INTERSECTION);
+
+    std::unique_ptr<Geometry> poly_inter_line = OverlayNGRobust::Overlay(
+        poly_union.get(),
+        a.getLineUnion(),
+        OverlayNG::INTERSECTION);
+
+    std::unique_ptr<Geometry> poly_inter_pt = OverlayNGRobust::Overlay(
+        poly_union.get(),
+        a.getPointUnion(),
+        OverlayNG::INTERSECTION);
+
+    std::unique_ptr<Geometry> line_inter_poly = OverlayNGRobust::Overlay(
+        line_union.get(),
+        a.getPolyUnion(),
+        OverlayNG::INTERSECTION);
+
+    std::unique_ptr<Geometry> line_inter_line = OverlayNGRobust::Overlay(
+        line_union.get(),
+        a.getLineUnion(),
+        OverlayNG::INTERSECTION);
 
-        return ret;
+    std::unique_ptr<Geometry> line_inter_pt = OverlayNGRobust::Overlay(
+        line_union.get(),
+        a.getPointUnion(),
+        OverlayNG::INTERSECTION);
+
+    std::unique_ptr<Geometry> pt_inter_pt = OverlayNGRobust::Overlay(
+        pt_union.get(),
+        a.getPointUnion(),
+        OverlayNG::INTERSECTION);
+
+    std::unique_ptr<Geometry> pt_inter_line = OverlayNGRobust::Overlay(
+        pt_union.get(),
+        a.getLineUnion(),
+        OverlayNG::INTERSECTION);
+
+    std::unique_ptr<Geometry> pt_inter_poly = OverlayNGRobust::Overlay(
+        pt_union.get(),
+        a.getPolyUnion(),
+        OverlayNG::INTERSECTION);
+
+    // io::WKTWriter w;
+    // std::cout << "poly_inter_poly " << w.write(*poly_inter_poly) << std::endl;
+    // std::cout << "poly_union.get() " << w.write(poly_union.get()) << std::endl;
+    // std::cout << "a.getLineUnion() " << w.write(a.getLineUnion()) << std::endl;
+    // std::cout << "poly_inter_line " << w.write(*poly_inter_line) << std::endl;
+    // std::cout << "poly_inter_pt " << w.write(*poly_inter_pt) << std::endl;
+    // std::cout << "line_inter_line " << w.write(*line_inter_line) << std::endl;
+    // std::cout << "line_inter_pt " << w.write(*line_inter_pt) << std::endl;
+    // std::cout << "pt_inter_pt " << w.write(*pt_inter_pt) << std::endl;
+
+    StructuredCollection c;
+    c.readCollection(poly_inter_poly.get());
+    c.readCollection(poly_inter_line.get());
+    c.readCollection(poly_inter_pt.get());
+    c.readCollection(line_inter_poly.get());
+    c.readCollection(line_inter_line.get());
+    c.readCollection(line_inter_pt.get());
+    c.readCollection(pt_inter_poly.get());
+    c.readCollection(pt_inter_line.get());
+    c.readCollection(pt_inter_pt.get());
+    c.unionByDimension();
+    return c.doUnaryUnion();
 }
 
+
+std::unique_ptr<Geometry>
+StructuredCollection::doDifference(const StructuredCollection& a) const
+{
+    std::unique_ptr<Geometry> poly_diff_poly = OverlayNGRobust::Overlay(
+        poly_union.get(),
+        a.getPolyUnion(),
+        OverlayNG::DIFFERENCE);
+
+    std::unique_ptr<Geometry> line_diff_poly = OverlayNGRobust::Overlay(
+        line_union.get(),
+        a.getPolyUnion(),
+        OverlayNG::DIFFERENCE);
+
+    std::unique_ptr<Geometry> pt_diff_poly = OverlayNGRobust::Overlay(
+        pt_union.get(),
+        a.getPolyUnion(),
+        OverlayNG::DIFFERENCE);
+
+    std::unique_ptr<Geometry> line_diff_poly_line = OverlayNGRobust::Overlay(
+        line_diff_poly.get(),
+        a.getLineUnion(),
+        OverlayNG::DIFFERENCE);
+
+    std::unique_ptr<Geometry> pt_diff_poly_line = OverlayNGRobust::Overlay(
+        pt_diff_poly.get(),
+        line_diff_poly_line.get(),
+        OverlayNG::DIFFERENCE);
+
+    std::unique_ptr<Geometry> pt_diff_poly_line_pt = OverlayNGRobust::Overlay(
+        pt_diff_poly_line.get(),
+        a.getPointUnion(),
+        OverlayNG::DIFFERENCE);
+
+    StructuredCollection c;
+    c.readCollection(poly_diff_poly.get());
+    c.readCollection(line_diff_poly_line.get());
+    c.readCollection(pt_diff_poly_line_pt.get());
+    c.unionByDimension();
+    return c.doUnaryUnion();
+}
+
+std::unique_ptr<Geometry>
+StructuredCollection::doSymDifference(const StructuredCollection& a) const
+{
+    std::unique_ptr<Geometry> poly_symdiff_poly = OverlayNGRobust::Overlay(
+        poly_union.get(),
+        a.getPolyUnion(),
+        OverlayNG::SYMDIFFERENCE);
+
+    std::unique_ptr<Geometry> line_symdiff_line = OverlayNGRobust::Overlay(
+        line_union.get(),
+        a.getLineUnion(),
+        OverlayNG::DIFFERENCE);
+
+    std::unique_ptr<Geometry> pt_symdiff_pt = OverlayNGRobust::Overlay(
+        pt_union.get(),
+        a.getPointUnion(),
+        OverlayNG::DIFFERENCE);
+
+    StructuredCollection c;
+    c.readCollection(poly_symdiff_poly.get());
+    c.readCollection(line_symdiff_line.get());
+    c.readCollection(pt_symdiff_pt.get());
+    c.unionByDimension();
+    return c.doUnaryUnion();
+}
+
+
 } // namespace geos::geom
 } // namespace geos


=====================================
tests/unit/geom/DimensionTest.cpp
=====================================
@@ -5,6 +5,7 @@
 #include <tut/tut.hpp>
 // geos
 #include <geos/geom/Dimension.h>
+#include <geos/io/WKTReader.h>
 #include <geos/util/IllegalArgumentException.h>
 #include <geos/util.h>
 
@@ -137,5 +138,21 @@ void object::test<5>
     }
 }
 
+template<>
+template<>
+void object::test<6>
+()
+{
+    using geos::geom::Dimension;
+    geos::io::WKTReader reader;
+    auto geom = reader.read("GEOMETRYCOLLECTION(POINT(1 1), LINESTRING(2 2, 3 3))");
+    Dimension::DimensionType d = geom->getDimension();
+    // std::cout << d << std::endl;
+    // getDimension() finds the highest dimension in the collection
+    ensure(d == Dimension::L);
+}
+
+
+
 } // namespace tut
 


=====================================
tests/unit/geom/HeuristicOverlayTest.cpp
=====================================
@@ -0,0 +1,207 @@
+//
+// Test Suite for geos::geom::HeuristicOverlay
+
+#include <tut/tut.hpp>
+
+// geos
+#include <geos/algorithm/hull/ConcaveHull.h>
+#include <geos/constants.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/HeuristicOverlay.h>
+#include <geos/io/WKTReader.h>
+#include <geos/io/WKTWriter.h>
+#include <geos/operation/overlayng/OverlayNG.h>
+#include <utility.h>
+
+// std
+#include <string>
+#include <memory>
+
+namespace tut {
+//
+// Test Group
+//
+
+using geos::operation::overlayng::OverlayNG;
+
+struct test_heuristic_data {
+
+    WKTReader reader_;
+    WKTWriter writer_;
+
+    test_heuristic_data() {}
+
+    void checkOverlay(
+        const std::string& wkt1,
+        const std::string& wkt2,
+        int opCode,
+        const std::string& wkt_expected)
+    {
+        std::unique_ptr<Geometry> g1 = reader_.read(wkt1);
+        std::unique_ptr<Geometry> g2 = reader_.read(wkt2);
+        std::unique_ptr<Geometry> expected = reader_.read(wkt_expected);
+        std::unique_ptr<Geometry> actual = HeuristicOverlay(g1.get(), g2.get(), opCode);
+
+        // std::cout << "expected:" << std::endl << writer_.write(*expected) << std::endl;
+        // std::cout << "actual:" << std::endl << writer_.write(*actual) << std::endl;
+
+        ensure_equals_geometry(expected.get(), actual.get());
+    }
+
+
+};
+
+typedef test_group<test_heuristic_data> group;
+typedef group::object object;
+
+group test_heuristic_data("geos::geom::HeuristicOverlay");
+
+//
+// Test Cases
+//
+
+//
+// These tests exercise the special cast code in HeuristicOverlay
+// for GeometryCollection in which the contents are "mixed dimension",
+// such as points and lines or lines and polygons in the same collection.
+// For those cases the result of the overlay might be a matter of
+// interpretation, depending on the inputs and the opinions of the
+// end user. The implementation just tries to generate a visually
+// defensible, simplified answer.
+//
+
+template<>
+template<>
+void object::test<1> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(1 1, 2 2))",
+        "GEOMETRYCOLLECTION(POINT(10 10), LINESTRING(11 11, 12 12))",
+        OverlayNG::UNION,
+        "GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(1 1, 2 2), POINT(10 10), LINESTRING(11 11, 12 12))"
+        );
+}
+
+template<>
+template<>
+void object::test<2> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(1 1, 2 2))",
+        "POLYGON((-10 -10, -10 10, 10 10, 10 -10, -10 -10))",
+        OverlayNG::UNION,
+        "POLYGON((-10 -10, -10 10, 10 10, 10 -10, -10 -10))"
+        );
+}
+
+template<>
+template<>
+void object::test<3> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POINT(0.5 0.5), LINESTRING(0 0, 2 2), POLYGON((0 0, 1 0, 1 1, 0 1, 0 0)))",
+        "GEOMETRYCOLLECTION(LINESTRING(0.5 0.5, 0.5 4), POINT(2 0))",
+        OverlayNG::UNION,
+        "GEOMETRYCOLLECTION (POINT (2 0), LINESTRING (0.5 1, 0.5 4), LINESTRING (1 1, 2 2), POLYGON ((0 1, 1 1, 1 0, 0 0, 0 1)))"
+        );
+}
+
+template<>
+template<>
+void object::test<4> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(20 20, 30 30))",
+        "GEOMETRYCOLLECTION(POLYGON((9 9, 21 9, 21 21, 9 21, 9 9)), POINT(5 5))",
+        OverlayNG::DIFFERENCE,
+        "GEOMETRYCOLLECTION (LINESTRING (21 21, 30 30), POLYGON ((10 0, 0 0, 0 10, 9 10, 9 9, 10 9, 10 0)))"
+        );
+}
+
+template<>
+template<>
+void object::test<5> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(20 20, 30 30))",
+        "GEOMETRYCOLLECTION(POLYGON((9 9, 21 9, 21 21, 9 21, 9 9)), POINT(5 5))",
+        OverlayNG::INTERSECTION,
+        "GEOMETRYCOLLECTION (POINT (5 5), LINESTRING(20 20, 21 21), POLYGON ((10 10, 10 9, 9 9, 9 10, 10 10)))"
+        );
+}
+
+template<>
+template<>
+void object::test<6> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(20 20, 30 30))",
+        "GEOMETRYCOLLECTION(POLYGON((9 9, 21 9, 21 21, 9 21, 9 9)), POINT(5 5))",
+        OverlayNG::SYMDIFFERENCE,
+        "GEOMETRYCOLLECTION (LINESTRING (21 21, 30 30), POLYGON ((0 0, 0 10, 9 10, 9 9, 10 9, 10 0, 0 0)), POLYGON ((9 10, 9 21, 21 21, 21 9, 10 9, 10 10, 9 10)))"
+        );
+}
+
+
+template<>
+template<>
+void object::test<7> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))",
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))",
+        OverlayNG::UNION,
+        "POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))"
+        );
+}
+
+template<>
+template<>
+void object::test<8> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))",
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), POINT(20 20))",
+        OverlayNG::DIFFERENCE,
+        "GEOMETRYCOLLECTION EMPTY"
+        );
+}
+
+template<>
+template<>
+void object::test<9> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))",
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))",
+        OverlayNG::INTERSECTION,
+        "POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))"
+        );
+}
+
+
+template<>
+template<>
+void object::test<10> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), POINT EMPTY, MULTIPOINT(4 4, 11 11), LINESTRING(5 5, 6 6))",
+        "GEOMETRYCOLLECTION(POLYGON((2 2, 12 2, 12 12, 2 12, 2 2)), LINESTRING EMPTY, MULTIPOINT(4 4, 11 11), LINESTRING(5 6, 6 5))",
+        OverlayNG::INTERSECTION,
+        "GEOMETRYCOLLECTION (POINT (11 11), POLYGON ((10 10, 10 2, 2 2, 2 10, 10 10)))"
+        );
+}
+
+template<>
+template<>
+void object::test<11> ()
+{
+    checkOverlay(
+        "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), POINT EMPTY, MULTIPOINT(4 4, 11 11), LINESTRING(5 5, 6 6))",
+        "GEOMETRYCOLLECTION(POLYGON((2 2, 12 2, 12 12, 2 12, 2 2)), LINESTRING EMPTY, MULTIPOINT(4 4, 11 11), LINESTRING(5 6, 6 5))",
+        OverlayNG::UNION,
+        "POLYGON ((2 12, 12 12, 12 2, 10 2, 10 0, 0 0, 0 10, 2 10, 2 12))"
+        );
+}
+
+} // namespace tut



View it on GitLab: https://salsa.debian.org/debian-gis-team/geos/-/commit/a8e816e46a2a3dcedfac2e6cc65b2a103bb0f823

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/geos/-/commit/a8e816e46a2a3dcedfac2e6cc65b2a103bb0f823
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20230615/df0bb9aa/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list