[SCM] PostGIS branch master updated. 3.6.0rc2-426-gae9559672
git at osgeo.org
git at osgeo.org
Wed Apr 1 12:04:56 PDT 2026
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 "PostGIS".
The branch, master has been updated
via ae9559672d2e329cee44bd697bac8411b94af13f (commit)
via 765787f0976adfe7ae83cbf4dabf7cb5188288b9 (commit)
via 515152e7bf193e6d75831e3794d9d0610d9c7dae (commit)
from 53280a4f683f696e8086609fd681d380519487b9 (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 ae9559672d2e329cee44bd697bac8411b94af13f
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date: Wed Apr 1 12:04:51 2026 -0700
Add news entry for ST_CoverageEdges
diff --git a/NEWS b/NEWS
index be561f67a..a0a1146c4 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,8 @@ This version requires GEOS 3.10 or higher
* New Features *
+ - ST_CoverageEdges, returns MultiLinestring of distinct shared edges in
+ polygonal coverage (Paul Ramsey)
- ST_MinimumSpanningTree, window function to calculate MST (Paul Ramsey)
- #5993, [topology] Add max_edges parameter to TopoGeo_AddLinestring
(Sandro Santilli)
commit 765787f0976adfe7ae83cbf4dabf7cb5188288b9
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date: Wed Apr 1 12:03:17 2026 -0700
Implement ST_CoverageEdges using GEOSCoverageEdges from GEOS 3.15
diff --git a/doc/reference_coverage.xml b/doc/reference_coverage.xml
index ccd8b868a..bad27160a 100644
--- a/doc/reference_coverage.xml
+++ b/doc/reference_coverage.xml
@@ -357,6 +357,76 @@ SELECT id, ST_AsText(ST_CoverageSimplify(geom, 30) OVER ())
</refentry>
+ <refentry xml:id="ST_CoverageEdges">
+ <refnamediv>
+ <refname>ST_CoverageEdges</refname>
+
+ <refpurpose>Computes the unique edges of a polygonal coverage.</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <funcsynopsis>
+ <funcprototype>
+ <funcdef>geometry <function>ST_CoverageEdges</function></funcdef>
+ <paramdef><type>geometry </type>
+ <parameter>geom</parameter></paramdef>
+ <paramdef choice="opt"><type>integer </type>
+ <parameter>edgetype = 0</parameter></paramdef>
+ </funcprototype>
+ <funcprototype>
+ <funcdef>geometry <function>ST_CoverageEdges</function></funcdef>
+ <paramdef><type>geometry[] </type>
+ <parameter>geoms</parameter></paramdef>
+ <paramdef choice="opt"><type>integer </type>
+ <parameter>edgetype = 0</parameter></paramdef>
+ </funcprototype>
+ </funcsynopsis>
+ </refsynopsisdiv>
+
+ <refsection>
+ <title>Description</title>
+
+ <para>Returns a MultiLineString representing the unique edges of a polygonal coverage.
+ A polygonal coverage is a set of non-overlapping polygons where adjacent polygons have matching vertices along shared edges.
+ </para>
+ <para>The <parameter>edgetype</parameter> parameter can be used to select which edges are returned:
+ <itemizedlist>
+ <listitem><para><emphasis role="bold">0 (ALL)</emphasis> - all unique edges (default)</para></listitem>
+ <listitem><para><emphasis role="bold">1 (EXTERIOR)</emphasis> - only exterior edges (non-shared)</para></listitem>
+ <listitem><para><emphasis role="bold">2 (INTERIOR)</emphasis> - only interior edges (shared)</para></listitem>
+ </itemizedlist>
+ </para>
+
+ <para role="availability" conformance="3.7.0">Availability: 3.7.0</para>
+ <para role="geos_requirement" conformance="3.15.0">Requires GEOS >= 3.15.0</para>
+ </refsection>
+
+ <refsection>
+ <title>Examples</title>
+ <programlisting>
+-- Get all unique edges of a coverage
+SELECT ST_AsText(ST_CoverageEdges(ST_GeomFromText('GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), POLYGON((10 0, 20 0, 20 10, 10 10, 10 0)))')));
+--------------------------------------
+MULTILINESTRING((0 0, 10 0), (10 0, 20 0), (20 0, 20 10), (20 10, 10 10), (10 10, 0 10), (0 10, 0 0), (10 0, 10 10))
+
+-- Get only interior (shared) edges
+SELECT ST_AsText(ST_CoverageEdges(ST_GeomFromText('GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), POLYGON((10 0, 20 0, 20 10, 10 10, 10 0)))'), 2));
+--------------------------------------
+MULTILINESTRING((10 0, 10 10))
+ </programlisting>
+ </refsection>
+
+ <refsection>
+ <title>See Also</title>
+ <para>
+ <xref linkend="ST_CoverageInvalidEdges"/>,
+ <xref linkend="ST_CoverageSimplify"/>,
+ <xref linkend="ST_CoverageUnion"/>
+ </para>
+ </refsection>
+
+ </refentry>
+
<refentry xml:id="ST_CoverageUnion">
<refnamediv>
<refname>ST_CoverageUnion</refname>
diff --git a/postgis/lwgeom_window.c b/postgis/lwgeom_window.c
index 2870527fe..be55bdaba 100644
--- a/postgis/lwgeom_window.c
+++ b/postgis/lwgeom_window.c
@@ -982,6 +982,7 @@ Datum ST_CoverageUnion(PG_FUNCTION_ARGS)
GEOSGeometry *geos = NULL;
GEOSGeometry *geos_result = NULL;
uint32 ngeoms = 0;
+ int srid = SRID_UNKNOWN;
ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
uint32 nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
@@ -1006,6 +1007,10 @@ Datum ST_CoverageUnion(PG_FUNCTION_ARGS)
gser = (GSERIALIZED *)DatumGetPointer(value);
if (gserialized_is_empty(gser)) continue;
+ /* Get SRID from first non-null element */
+ if (srid == SRID_UNKNOWN)
+ srid = gserialized_get_srid(gser);
+
/* Omit unconvertible */
geos = POSTGIS2GEOS(gser);
if (!geos) continue;
@@ -1015,7 +1020,9 @@ Datum ST_CoverageUnion(PG_FUNCTION_ARGS)
array_free_iterator(iterator);
if (ngeoms == 0)
+ {
PG_RETURN_NULL();
+ }
geos = GEOSGeom_createCollection(
GEOS_GEOMETRYCOLLECTION,
@@ -1027,6 +1034,8 @@ Datum ST_CoverageUnion(PG_FUNCTION_ARGS)
HANDLE_GEOS_ERROR("Geometry could not be converted");
}
+ GEOSSetSRID(geos, srid);
+
geos_result = GEOSCoverageUnion(geos);
GEOSGeom_destroy(geos);
if (!geos_result)
@@ -1038,6 +1047,139 @@ Datum ST_CoverageUnion(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result);
}
+
+/**********************************************************************
+ * ST_CoverageEdges(geometry, edgetype)
+ * ST_CoverageEdges(geometry[], edgetype)
+ *
+ */
+
+Datum ST_CoverageEdges(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(ST_CoverageEdges);
+Datum ST_CoverageEdges(PG_FUNCTION_ARGS)
+{
+#if POSTGIS_GEOS_VERSION < 31500
+ lwpgerror("The GEOS version this PostGIS binary "
+ "was compiled against (%d) doesn't support "
+ "'ST_CoverageEdges' function (3.15.0+ required)",
+ POSTGIS_GEOS_VERSION);
+ PG_RETURN_NULL();
+#else
+ GSERIALIZED *result = NULL;
+ GSERIALIZED *gser = PG_GETARG_GSERIALIZED_P(0);
+ int edgetype = PG_GETARG_INT32(1);
+ GEOSGeometry *geos = NULL;
+ GEOSGeometry *geos_result = NULL;
+
+ if (gserialized_is_empty(gser))
+ PG_RETURN_NULL();
+
+ initGEOS(lwpgnotice, lwgeom_geos_error);
+
+ geos = POSTGIS2GEOS(gser);
+ if (!geos)
+ HANDLE_GEOS_ERROR("Geometry could not be converted");
+
+ geos_result = GEOSCoverageEdges(geos, edgetype);
+ GEOSGeom_destroy(geos);
+ if (!geos_result)
+ HANDLE_GEOS_ERROR("Error computing coverage edges");
+
+ result = GEOS2POSTGIS(geos_result, LW_FALSE);
+ GEOSGeom_destroy(geos_result);
+
+ PG_RETURN_POINTER(result);
+#endif
+}
+
+Datum ST_CoverageEdges_array(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(ST_CoverageEdges_array);
+Datum ST_CoverageEdges_array(PG_FUNCTION_ARGS)
+{
+#if POSTGIS_GEOS_VERSION < 31500
+ lwpgerror("The GEOS version this PostGIS binary "
+ "was compiled against (%d) doesn't support "
+ "'ST_CoverageEdges' function (3.15.0+ required)",
+ POSTGIS_GEOS_VERSION);
+ PG_RETURN_NULL();
+#else
+ GSERIALIZED *result = NULL;
+ int edgetype = PG_GETARG_INT32(1);
+
+ Datum value;
+ bool isnull;
+
+ GEOSGeometry **geoms = NULL;
+ GEOSGeometry *geos = NULL;
+ GEOSGeometry *geos_result = NULL;
+ uint32 ngeoms = 0;
+ int srid = SRID_UNKNOWN;
+
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
+ uint32 nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
+ ArrayIterator iterator = array_create_iterator(array, 0, NULL);
+
+ /* Return null on 0-elements input array */
+ if (nelems == 0)
+ PG_RETURN_NULL();
+
+ /* Convert all geometries into GEOSGeometry array */
+ geoms = palloc(sizeof(GEOSGeometry *) * nelems);
+
+ initGEOS(lwpgnotice, lwgeom_geos_error);
+
+ while (array_iterate(iterator, &value, &isnull))
+ {
+ GSERIALIZED *gser;
+ /* Omit nulls */
+ if (isnull) continue;
+
+ /* Omit empty */
+ gser = (GSERIALIZED *)DatumGetPointer(value);
+ if (gserialized_is_empty(gser)) continue;
+
+ /* Get SRID from first non-null element */
+ if (srid == SRID_UNKNOWN)
+ srid = gserialized_get_srid(gser);
+
+ /* Omit unconvertible */
+ geos = POSTGIS2GEOS(gser);
+ if (!geos) continue;
+
+ geoms[ngeoms++] = geos;
+ }
+ array_free_iterator(iterator);
+
+ if (ngeoms == 0)
+ {
+ PG_RETURN_NULL();
+ }
+
+ geos = GEOSGeom_createCollection(
+ GEOS_GEOMETRYCOLLECTION,
+ geoms, ngeoms);
+
+ if (!geos)
+ {
+ coverage_destroy_geoms(geoms, ngeoms);
+ HANDLE_GEOS_ERROR("Geometry could not be converted");
+ }
+
+ GEOSSetSRID(geos, srid);
+
+ geos_result = GEOSCoverageEdges(geos, edgetype);
+ GEOSGeom_destroy(geos);
+ if (!geos_result)
+ HANDLE_GEOS_ERROR("Error computing coverage edges");
+
+ result = GEOS2POSTGIS(geos_result, LW_FALSE);
+ GEOSGeom_destroy(geos_result);
+
+ PG_RETURN_POINTER(result);
+#endif
+}
+
+
extern Datum ST_MinimumSpanningTree(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(ST_MinimumSpanningTree);
Datum ST_MinimumSpanningTree(PG_FUNCTION_ARGS)
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index 41a7ee22a..649ed3fc5 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -4498,6 +4498,20 @@ CREATE OR REPLACE FUNCTION ST_CoverageClean (geom geometry, gapMaximumWidth floa
LANGUAGE 'c' IMMUTABLE STRICT WINDOW PARALLEL SAFE
_COST_HIGH;
+-- Availability: 3.7.0
+CREATE OR REPLACE FUNCTION ST_CoverageEdges (geoms geometry[], edgetype integer default 0)
+ RETURNS geometry
+ AS 'MODULE_PATHNAME', 'ST_CoverageEdges_array'
+ LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+ _COST_HIGH;
+
+-- Availability: 3.7.0
+CREATE OR REPLACE FUNCTION ST_CoverageEdges (geom geometry, edgetype integer default 0)
+ RETURNS geometry
+ AS 'MODULE_PATHNAME', 'ST_CoverageEdges'
+ LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+ _COST_HIGH;
+
--------------------------------------------------------------------------------
-- Availability: 2.3.0
diff --git a/regress/core/coverage.sql b/regress/core/coverage.sql
index 9c456410c..159022e83 100644
--- a/regress/core/coverage.sql
+++ b/regress/core/coverage.sql
@@ -101,3 +101,29 @@ FROM squares
GROUP BY i, j
ORDER By i, j;
+------------------------------------------------------------------------
+
+CREATE TABLE coverage_edges (id integer, seq integer, geom geometry);
+
+INSERT INTO coverage_edges VALUES
+(6, 1, 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))'),
+(6, 2, 'POLYGON ((10 0, 10 10, 20 10, 20 0, 10 0))');
+
+SELECT 'coverage edges all' AS test,
+ ST_AsText(ST_CoverageEdges(ST_Collect(geom)))
+FROM coverage_edges WHERE id = 6;
+
+SELECT 'coverage edges exterior' AS test,
+ ST_AsText(ST_CoverageEdges(ST_Collect(geom), 1))
+FROM coverage_edges WHERE id = 6;
+
+SELECT 'coverage edges interior' AS test,
+ ST_AsText(ST_CoverageEdges(ST_Collect(geom), 2))
+FROM coverage_edges WHERE id = 6;
+
+SELECT 'coverage edges array' AS test,
+ ST_AsText(ST_CoverageEdges(array_agg(geom)))
+FROM coverage_edges WHERE id = 6;
+
+DROP TABLE coverage_edges;
+
diff --git a/regress/core/coverage_expected b/regress/core/coverage_expected
index 2b456107a..2a0304871 100644
--- a/regress/core/coverage_expected
+++ b/regress/core/coverage_expected
@@ -36,3 +36,7 @@ grid squares|1|2|50000
grid squares|2|0|50000
grid squares|2|1|50000
grid squares|2|2|10000
+coverage edges all|MULTILINESTRING((10 10,10 0),(10 0,0 0,0 10,10 10),(10 10,20 10,20 0,10 0))
+coverage edges exterior|MULTILINESTRING((10 0,0 0,0 10,10 10),(10 10,20 10,20 0,10 0))
+coverage edges interior|MULTILINESTRING((10 10,10 0))
+coverage edges array|MULTILINESTRING((10 10,10 0),(10 0,0 0,0 10,10 10),(10 10,20 10,20 0,10 0))
commit 515152e7bf193e6d75831e3794d9d0610d9c7dae
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date: Wed Apr 1 10:33:30 2026 -0700
Simplify stringbuffer handling a little
diff --git a/loader/shp2pgsql-core.c b/loader/shp2pgsql-core.c
index 8db842117..9065bdf6f 100644
--- a/loader/shp2pgsql-core.c
+++ b/loader/shp2pgsql-core.c
@@ -1479,8 +1479,7 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader)
}
/* Copy the string buffer into a new string, destroying the string buffer */
- ret = (char *)malloc(strlen((char *)stringbuffer_getstring(sb)) + 1);
- strcpy(ret, (char *)stringbuffer_getstring(sb));
+ ret = strdup(stringbuffer_getstring(sb));
stringbuffer_destroy(sb);
*strheader = ret;
@@ -1519,8 +1518,7 @@ ShpLoaderGetSQLCopyStatement(SHPLOADERSTATE *state, char **strheader)
}
/* Copy the string buffer into a new string, destroying the string buffer */
- ret = (char *)malloc(strlen((char *)stringbuffer_getstring(sb)) + 1);
- strcpy(ret, (char *)stringbuffer_getstring(sb));
+ ret = strdup(stringbuffer_getstring(sb));
stringbuffer_destroy(sb);
*strheader = ret;
@@ -1851,14 +1849,13 @@ done_cell:
/* Copy the string buffer into a new string, destroying the string buffer */
- ret = (char *)malloc(strlen((char *)stringbuffer_getstring(sb)) + 1);
- strcpy(ret, (char *)stringbuffer_getstring(sb));
+ ret = strdup(stringbuffer_getstring(sb));
stringbuffer_destroy(sb);
*strrecord = ret;
/* If any warnings occurred, set the returned message string and warning status */
- if (strlen((char *)stringbuffer_getstring(sbwarn)) > 0)
+ if (stringbuffer_getlength(sbwarn) > 0)
{
snprintf(state->message, SHPLOADERMSGLEN, "%s", stringbuffer_getstring(sbwarn));
stringbuffer_destroy(sbwarn);
@@ -1943,8 +1940,7 @@ ShpLoaderGetSQLFooter(SHPLOADERSTATE *state, char **strfooter)
}
/* Copy the string buffer into a new string, destroying the string buffer */
- ret = (char *)malloc(strlen((char *)stringbuffer_getstring(sb)) + 1);
- strcpy(ret, (char *)stringbuffer_getstring(sb));
+ ret = strdup(stringbuffer_getstring(sb));
stringbuffer_destroy(sb);
*strfooter = ret;
-----------------------------------------------------------------------
Summary of changes:
NEWS | 2 +
doc/reference_coverage.xml | 70 ++++++++++++++++++++
loader/shp2pgsql-core.c | 14 ++--
postgis/lwgeom_window.c | 142 +++++++++++++++++++++++++++++++++++++++++
postgis/postgis.sql.in | 14 ++++
regress/core/coverage.sql | 26 ++++++++
regress/core/coverage_expected | 4 ++
7 files changed, 263 insertions(+), 9 deletions(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list