[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