[postgis-tickets] [SCM] PostGIS branch master updated. 3.3.0rc2-681-gd3cf06c2e

git at osgeo.org git at osgeo.org
Wed Mar 1 12:36:36 PST 2023


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  d3cf06c2e0366ec1e5461b0ed50f4106cbdf44c6 (commit)
      from  9f38f0b082189c5b79b66db71947765aa7e0e6de (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 d3cf06c2e0366ec1e5461b0ed50f4106cbdf44c6
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Wed Mar 1 12:34:44 2023 -0800

    Add ST_ClusterIntersectingWin and ST_ClusterWithinWin, windowing
    function analogues to ST_ClusterIntersecting and ST_ClusterWithin.
    New functions return a cluster id within the current window partition,
    which should be easier to work with than the large collections
    returned by the older functions.

diff --git a/.gitignore b/.gitignore
index b928337a8..99dfaaeed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ aclocal.m4
 autom4te.cache/
 build/
 build-aux/
+config
 config.log
 config.guess
 config.sub
diff --git a/NEWS b/NEWS
index d24bd9315..30bfa9001 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,7 @@ xxxx/xx/xx
   - GH703, Add min/max resampling as options (Christian Schroeder)
   - #5336, topogeometry cast to topoelement support (Regina Obe)
   - Allow singleton geometry to be inserted into Geometry(Multi*) columns (Paul Ramsey)
+  - GH721, New window-based ST_ClusterWithinWin and ST_ClusterIntersectingWin (Paul Ramsey)
 
 * Enhancements *
   - #5194, do not update system catalogs from postgis_extensions_upgrade (Sandro Santilli)
diff --git a/doc/reference_cluster.xml b/doc/reference_cluster.xml
index 860a3f8ec..47db8ceaf 100644
--- a/doc/reference_cluster.xml
+++ b/doc/reference_cluster.xml
@@ -70,6 +70,7 @@
 	  </para></note>
 
       <para>Availability: 2.3.0</para>
+      <para>&curve_support;</para>
     </refsection>
 
     <refsection>
@@ -145,16 +146,80 @@ GROUP BY cid;
               <xref linkend="ST_ClusterKMeans"/>,
               <xref linkend="ST_ClusterIntersecting"/>,
               <xref linkend="ST_ClusterWithin"/>
+              <xref linkend="ST_ClusterIntersectingWin"/>
           </para>
 	  </refsection>
 
     </refentry>
 
+    <refentry id="ST_ClusterIntersectingWin">
+      <refnamediv>
+        <refname>ST_ClusterIntersectingWin</refname>
+
+        <refpurpose>Window function that returns a cluster id for each input geometry, clustering input geometries into connected sets.</refpurpose>
+      </refnamediv>
+
+      <refsynopsisdiv>
+        <funcsynopsis>
+          <funcprototype>
+            <funcdef>integer <function>ST_ClusterIntersectingWin</function></funcdef>
+            <paramdef><type>geometry winset </type> <parameter>geom</parameter></paramdef>
+          </funcprototype>
+        </funcsynopsis>
+      </refsynopsisdiv>
+
+      <refsection>
+        <title>Description</title>
+
+        <para>ST_ClusterIntersectingWin is a windowing function that builds inter-connecting clusters of geometries that intersect. It is possible to traverse all geometries in a cluster without leaving the cluster. The return value is the cluster number that the geometry argument participates in, or null for null inputs.</para>
+
+        <para>Availability: 3.4.0</para>
+      </refsection>
+
+      <refsection>
+        <title>Examples</title>
+        <programlisting>
+WITH testdata AS (
+  SELECT id, geom::geometry FROM (
+  VALUES  (1, 'LINESTRING (0 0, 1 1)'),
+          (2, 'LINESTRING (5 5, 4 4)'),
+          (3, 'LINESTRING (6 6, 7 7)'),
+          (4, 'LINESTRING (0 0, -1 -1)'),
+          (5, 'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))')) AS t(id, geom)
+)
+SELECT id, 
+  ST_AsText(geom), 
+  ST_ClusterIntersectingWin(geom) OVER () AS cluster 
+FROM testdata;
+
+ id |           st_astext            | cluster 
+----+--------------------------------+---------
+  1 | LINESTRING(0 0,1 1)            |       0
+  2 | LINESTRING(5 5,4 4)            |       0
+  3 | LINESTRING(6 6,7 7)            |       1
+  4 | LINESTRING(0 0,-1 -1)          |       0
+  5 | POLYGON((0 0,4 0,4 4,0 4,0 0)) |       0
+
+        </programlisting>
+      </refsection>
+      <refsection>
+        <title>See Also</title>
+        <para>
+            <xref linkend="ST_ClusterDBSCAN" />,
+            <xref linkend="ST_ClusterKMeans" />,
+            <xref linkend="ST_ClusterWithinWin" />
+            <xref linkend="ST_ClusterWithin" />
+            <xref linkend="ST_ClusterIntersecting" />
+        </para>
+      </refsection>
+
+    </refentry>
+
     <refentry id="ST_ClusterIntersecting">
       <refnamediv>
         <refname>ST_ClusterIntersecting</refname>
 
-        <refpurpose>Aggregate function that clusters the input geometries into connected sets.</refpurpose>
+        <refpurpose>Aggregate function that clusters input geometries into connected sets.</refpurpose>
       </refnamediv>
 
       <refsynopsisdiv>
@@ -179,10 +244,10 @@ GROUP BY cid;
         <programlisting>
 WITH testdata AS
   (SELECT unnest(ARRAY['LINESTRING (0 0, 1 1)'::geometry,
-		       'LINESTRING (5 5, 4 4)'::geometry,
-		       'LINESTRING (6 6, 7 7)'::geometry,
-		       'LINESTRING (0 0, -1 -1)'::geometry,
-		       'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))'::geometry]) AS geom)
+           'LINESTRING (5 5, 4 4)'::geometry,
+           'LINESTRING (6 6, 7 7)'::geometry,
+           'LINESTRING (0 0, -1 -1)'::geometry,
+           'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))'::geometry]) AS geom)
 
 SELECT ST_AsText(unnest(ST_ClusterIntersecting(geom))) FROM testdata;
 
@@ -199,6 +264,7 @@ GEOMETRYCOLLECTION(LINESTRING(6 6,7 7))
         <para>
             <xref linkend="ST_ClusterDBSCAN" />,
             <xref linkend="ST_ClusterKMeans" />,
+            <xref linkend="ST_ClusterWithinWin" />
             <xref linkend="ST_ClusterWithin" />
         </para>
       </refsection>
@@ -206,6 +272,11 @@ GEOMETRYCOLLECTION(LINESTRING(6 6,7 7))
     </refentry>
 
 
+
+
+
+
+
 	<refentry id="ST_ClusterKMeans">
 	  <refnamediv>
 		<refname>ST_ClusterKMeans</refname>
@@ -334,6 +405,8 @@ from
 		  <title>See Also</title>
           <para>
               <xref linkend="ST_ClusterDBSCAN"/>,
+              <xref linkend="ST_ClusterIntersectingWin"/>,
+              <xref linkend="ST_ClusterWithinWin" />,
               <xref linkend="ST_ClusterIntersecting" />,
               <xref linkend="ST_ClusterWithin" />,
               <xref linkend="ST_Subdivide" />,
@@ -347,7 +420,7 @@ from
       <refnamediv>
         <refname>ST_ClusterWithin</refname>
 
-        <refpurpose>Aggregate function that clusters the input geometries by separation distance.</refpurpose>
+        <refpurpose>Aggregate function that clusters input geometries by separation distance.</refpurpose>
       </refnamediv>
 
       <refsynopsisdiv>
@@ -366,6 +439,7 @@ from
         <para>ST_ClusterWithin is an aggregate function that returns an array of GeometryCollections, where each GeometryCollection represents a set of geometries separated by no more than the specified distance.  (Distances are Cartesian distances in the units of the SRID.)</para>
 
         <para>Availability: 2.2.0</para>
+        <para>&curve_support;</para>
       </refsection>
 
       <refsection>
@@ -393,7 +467,77 @@ GEOMETRYCOLLECTION(LINESTRING(6 6,7 7))
         <para>
           <xref linkend="ST_ClusterDBSCAN" />,
           <xref linkend="ST_ClusterKMeans" />,
-          <xref linkend="ST_ClusterIntersecting" />
+          <xref linkend="ST_ClusterIntersectingWin"/>,
+          <xref linkend="ST_ClusterWithinWin"/>,
+          <xref linkend="ST_ClusterIntersecting"/>
+        </para>
+      </refsection>
+
+    </refentry>
+
+  <refentry id="ST_ClusterWithinWin">
+      <refnamediv>
+        <refname>ST_ClusterWithinWin</refname>
+
+        <refpurpose>Window function that returns a cluster id for each input geometry, clustering using separation distance.</refpurpose>
+      </refnamediv>
+
+      <refsynopsisdiv>
+        <funcsynopsis>
+          <funcprototype>
+            <funcdef>integer <function>ST_ClusterWithinWin</function></funcdef>
+            <paramdef><type>geometry winset </type> <parameter>geom</parameter></paramdef>
+            <paramdef><type>float8 </type> <parameter>distance</parameter></paramdef>
+          </funcprototype>
+        </funcsynopsis>
+      </refsynopsisdiv>
+
+      <refsection>
+        <title>Description</title>
+
+        <para>ST_ClusterWithinWin is a window function that returns an integer for each input geometry, where the integer the cluster number the geometry is a member of. Clusters represent a set of input geometries separated by no more than the specified distance. (Distances are Cartesian distances in the units of the SRID.)
+        </para>
+        <para>ST_ClusterWithinWin is equivalent to running <xref linkend="ST_ClusterDBSCAN" /> with a `minpoints` of zero.</para>
+        <para>Availability: 3.4.0</para>
+        <para>&curve_support;</para>
+
+      </refsection>
+
+      <refsection>
+        <title>Examples</title>
+        <programlisting>
+WITH testdata AS (
+  SELECT id, geom::geometry FROM (
+  VALUES  (1, 'LINESTRING (0 0, 1 1)'),
+          (2, 'LINESTRING (5 5, 4 4)'),
+          (3, 'LINESTRING (6 6, 7 7)'),
+          (4, 'LINESTRING (0 0, -1 -1)'),
+          (5, 'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))')) AS t(id, geom)
+)
+SELECT id, 
+  ST_AsText(geom), 
+  ST_ClusterWithinWin(geom, 1.4) OVER () AS cluster 
+FROM testdata;
+
+
+ id |           st_astext            | cluster 
+----+--------------------------------+---------
+  1 | LINESTRING(0 0,1 1)            |       0
+  2 | LINESTRING(5 5,4 4)            |       0
+  3 | LINESTRING(6 6,7 7)            |       1
+  4 | LINESTRING(0 0,-1 -1)          |       0
+  5 | POLYGON((0 0,4 0,4 4,0 4,0 0)) |       0
+
+        </programlisting>
+      </refsection>
+      <refsection>
+        <title>See Also</title>
+        <para>
+          <xref linkend="ST_ClusterDBSCAN" />,
+          <xref linkend="ST_ClusterKMeans" />,
+          <xref linkend="ST_ClusterIntersectingWin"/>,
+          <xref linkend="ST_ClusterWithin"/>,
+          <xref linkend="ST_ClusterIntersecting"/>
         </para>
       </refsection>
 
diff --git a/liblwgeom/lwgeom_geos.h b/liblwgeom/lwgeom_geos.h
index 71ec5fe47..bf2f94bf3 100644
--- a/liblwgeom/lwgeom_geos.h
+++ b/liblwgeom/lwgeom_geos.h
@@ -43,6 +43,7 @@ GEOSGeometry* make_geos_point(double x, double y);
 GEOSGeometry* make_geos_segment(double x1, double y1, double x2, double y2);
 
 int cluster_intersecting(GEOSGeometry **geoms, uint32_t num_geoms, GEOSGeometry ***clusterGeoms, uint32_t *num_clusters);
+int union_intersecting_pairs(GEOSGeometry** geoms, uint32_t num_geoms, UNIONFIND* uf);
 int cluster_within_distance(LWGEOM **geoms, uint32_t num_geoms, double tolerance, LWGEOM ***clusterGeoms, uint32_t *num_clusters);
 int union_dbscan(LWGEOM **geoms, uint32_t num_geoms, UNIONFIND *uf, double eps, uint32_t min_points, char **is_in_cluster_ret);
 
diff --git a/liblwgeom/lwgeom_geos_cluster.c b/liblwgeom/lwgeom_geos_cluster.c
index 7e1b6f607..091de6bf1 100644
--- a/liblwgeom/lwgeom_geos_cluster.c
+++ b/liblwgeom/lwgeom_geos_cluster.c
@@ -50,7 +50,6 @@ struct STRTree
 
 static struct STRTree make_strtree(void** geoms, uint32_t num_geoms, char is_lwgeom);
 static void destroy_strtree(struct STRTree * tree);
-static int union_intersecting_pairs(GEOSGeometry** geoms, uint32_t num_geoms, UNIONFIND* uf);
 static int combine_geometries(UNIONFIND* uf, void** geoms, uint32_t num_geoms, void*** clustersGeoms, uint32_t* num_clusters, char is_lwgeom);
 
 /* Make a minimal GEOSGeometry* whose Envelope covers the same 2D extent as
@@ -155,7 +154,7 @@ query_accumulate(void* item, void* userdata)
 }
 
 /* Identify intersecting geometries and mark them as being in the same set */
-static int
+int
 union_intersecting_pairs(GEOSGeometry** geoms, uint32_t num_geoms, UNIONFIND* uf)
 {
 	uint32_t p, i;
diff --git a/postgis/lwgeom_window.c b/postgis/lwgeom_window.c
index 7a715914e..4fd14199d 100644
--- a/postgis/lwgeom_window.c
+++ b/postgis/lwgeom_window.c
@@ -39,6 +39,8 @@
 
 extern Datum ST_ClusterDBSCAN(PG_FUNCTION_ARGS);
 extern Datum ST_ClusterKMeans(PG_FUNCTION_ARGS);
+extern Datum ST_ClusterWithinWin(PG_FUNCTION_ARGS);
+extern Datum ST_ClusterIntersectingWin(PG_FUNCTION_ARGS);
 
 typedef struct {
 	bool	isdone;
@@ -53,13 +55,21 @@ typedef struct
 	char is_null;        /* NULL may result from a NULL geometry input, or it may be used by
 							algorithms such as DBSCAN that do not assign all inputs to a
 							cluster. */
-} dbscan_cluster_result;
+} cluster_entry;
 
 typedef struct
 {
 	char is_error;
-	dbscan_cluster_result cluster_assignments[1];
-} dbscan_context;
+	cluster_entry clusters[1];
+} cluster_context;
+
+static cluster_context*
+fetch_cluster_context(WindowObject win_obj, uint32_t ngeoms)
+{
+	size_t context_sz = sizeof(cluster_context) + (ngeoms * sizeof(cluster_entry));
+	cluster_context* context = WinGetPartitionLocalMemory(win_obj, context_sz);
+	return context;
+}
 
 static LWGEOM*
 read_lwgeom_from_partition(WindowObject win_obj, uint32_t i, bool* is_null)
@@ -79,13 +89,41 @@ read_lwgeom_from_partition(WindowObject win_obj, uint32_t i, bool* is_null)
 	return lwgeom_from_gserialized(g);
 }
 
+static GEOSGeometry*
+read_geos_from_partition(WindowObject win_obj, uint32_t i, bool* is_null)
+{
+	GSERIALIZED* g;
+	LWGEOM* lwg;
+	GEOSGeometry* gg;
+	Datum arg = WinGetFuncArgInPartition(win_obj, 0, i, WINDOW_SEEK_HEAD, false, is_null, NULL);
+
+	if (*is_null) {
+		/* So that the indexes in our clustering input array can match our partition positions,
+		 * toss an empty point into the clustering inputs, as a pass-through.
+		 * NOTE: this will cause gaps in the output cluster id sequence.
+		 * */
+		lwg = lwpoint_as_lwgeom(lwpoint_construct_empty(0, 0, 0));
+		gg = LWGEOM2GEOS(lwg, LW_FALSE);
+		lwgeom_free(lwg);
+		return gg;
+	}
+
+	g = (GSERIALIZED*) PG_DETOAST_DATUM_COPY(arg);
+	lwg = lwgeom_from_gserialized(g);
+	gg = LWGEOM2GEOS(lwg, LW_TRUE);
+	lwgeom_free(lwg);
+	if (!gg) {
+		*is_null = LW_TRUE;
+	}
+	return gg;
+}
 PG_FUNCTION_INFO_V1(ST_ClusterDBSCAN);
 Datum ST_ClusterDBSCAN(PG_FUNCTION_ARGS)
 {
 	WindowObject win_obj = PG_WINDOW_OBJECT();
 	uint32_t row = WinGetCurrentPosition(win_obj);
 	uint32_t ngeoms = WinGetPartitionRowCount(win_obj);
-	dbscan_context* context = WinGetPartitionLocalMemory(win_obj, sizeof(dbscan_context) + ngeoms * sizeof(dbscan_cluster_result));
+	cluster_context* context = fetch_cluster_context(win_obj, ngeoms);
 
 	if (row == 0) /* beginning of the partition; do all of the work now */
 	{
@@ -119,7 +157,9 @@ Datum ST_ClusterDBSCAN(PG_FUNCTION_ARGS)
 		uf = UF_create(ngeoms);
 		for (i = 0; i < ngeoms; i++)
 		{
-			geoms[i] = read_lwgeom_from_partition(win_obj, i, (bool*)&(context->cluster_assignments[i].is_null));
+			bool geom_is_null;
+			geoms[i] = read_lwgeom_from_partition(win_obj, i, &geom_is_null);
+			context->clusters[i].is_null = geom_is_null;
 
 			if (!geoms[i]) {
 				/* TODO release memory ? */
@@ -151,24 +191,165 @@ Datum ST_ClusterDBSCAN(PG_FUNCTION_ARGS)
 		{
 			if (minpoints > 1 && !is_in_cluster[i])
 			{
-				context->cluster_assignments[i].is_null = LW_TRUE;
+				context->clusters[i].is_null = LW_TRUE;
 			}
 			else
 			{
-				context->cluster_assignments[i].cluster_id = result_ids[i];
+				context->clusters[i].cluster_id = result_ids[i];
+			}
+		}
+
+		lwfree(result_ids);
+		UF_destroy(uf);
+	}
+
+	if (context->clusters[row].is_null)
+		PG_RETURN_NULL();
+
+	PG_RETURN_INT32(context->clusters[row].cluster_id);
+}
+
+
+PG_FUNCTION_INFO_V1(ST_ClusterWithinWin);
+Datum ST_ClusterWithinWin(PG_FUNCTION_ARGS)
+{
+	WindowObject win_obj = PG_WINDOW_OBJECT();
+	uint32_t row = WinGetCurrentPosition(win_obj);
+	uint32_t ngeoms = WinGetPartitionRowCount(win_obj);
+	cluster_context* context = fetch_cluster_context(win_obj, ngeoms);
+
+	if (row == 0) /* beginning of the partition; do all of the work now */
+	{
+		uint32_t i;
+		uint32_t* result_ids;
+		LWGEOM** geoms;
+		UNIONFIND* uf;
+		bool tolerance_is_null;
+		double tolerance = DatumGetFloat8(WinGetFuncArgCurrent(win_obj, 1, &tolerance_is_null));
+
+		/* Validate input parameters */
+		if (tolerance_is_null || tolerance < 0)
+		{
+			lwpgerror("Tolerance must be a positive number", tolerance);
+			PG_RETURN_NULL();
+		}
+
+		context->is_error = LW_TRUE; /* until proven otherwise */
+
+		geoms = lwalloc(ngeoms * sizeof(LWGEOM*));
+		uf = UF_create(ngeoms);
+		for (i = 0; i < ngeoms; i++)
+		{
+			bool geom_is_null;
+			geoms[i] = read_lwgeom_from_partition(win_obj, i, &geom_is_null);
+			context->clusters[i].is_null = geom_is_null;
+
+			if (!geoms[i])
+			{
+				lwpgerror("Error reading geometry.");
+				PG_RETURN_NULL();
+			}
+		}
+
+		initGEOS(lwpgnotice, lwgeom_geos_error);
+
+		if (union_dbscan(geoms, ngeoms, uf, tolerance, 1, NULL) == LW_SUCCESS)
+			context->is_error = LW_FALSE;
+
+		for (i = 0; i < ngeoms; i++)
+		{
+			lwgeom_free(geoms[i]);
+		}
+		lwfree(geoms);
+
+		if (context->is_error)
+		{
+			UF_destroy(uf);
+			lwpgerror("Error during clustering");
+			PG_RETURN_NULL();
+		}
+
+		result_ids = UF_get_collapsed_cluster_ids(uf, NULL);
+		for (i = 0; i < ngeoms; i++)
+		{
+			context->clusters[i].cluster_id = result_ids[i];
+		}
+
+		lwfree(result_ids);
+		UF_destroy(uf);
+	}
+
+	if (context->clusters[row].is_null)
+		PG_RETURN_NULL();
+
+	PG_RETURN_INT32(context->clusters[row].cluster_id);
+}
+
+PG_FUNCTION_INFO_V1(ST_ClusterIntersectingWin);
+Datum ST_ClusterIntersectingWin(PG_FUNCTION_ARGS)
+{
+	WindowObject win_obj = PG_WINDOW_OBJECT();
+	uint32_t row = WinGetCurrentPosition(win_obj);
+	uint32_t ngeoms = WinGetPartitionRowCount(win_obj);
+	cluster_context* context = fetch_cluster_context(win_obj, ngeoms);
+
+	if (row == 0) /* beginning of the partition; do all of the work now */
+	{
+		uint32_t i;
+		uint32_t* result_ids;
+		GEOSGeometry** geoms = lwalloc(ngeoms * sizeof(GEOSGeometry*));
+		UNIONFIND* uf = UF_create(ngeoms);
+
+		context->is_error = LW_TRUE; /* until proven otherwise */
+
+		initGEOS(lwpgnotice, lwgeom_geos_error);
+
+		for (i = 0; i < ngeoms; i++)
+		{
+			bool geom_is_null;
+			geoms[i] = read_geos_from_partition(win_obj, i, &geom_is_null);
+			context->clusters[i].is_null = geom_is_null;
+
+			if (!geoms[i])
+			{
+				lwpgerror("Error reading geometry.");
+				PG_RETURN_NULL();
 			}
 		}
 
+		if (union_intersecting_pairs(geoms, ngeoms, uf) == LW_SUCCESS)
+			context->is_error = LW_FALSE;
+
+		for (i = 0; i < ngeoms; i++)
+		{
+			GEOSGeom_destroy(geoms[i]);
+		}
+		lwfree(geoms);
+
+		if (context->is_error)
+		{
+			UF_destroy(uf);
+			lwpgerror("Error during clustering");
+			PG_RETURN_NULL();
+		}
+
+		result_ids = UF_get_collapsed_cluster_ids(uf, NULL);
+		for (i = 0; i < ngeoms; i++)
+		{
+			context->clusters[i].cluster_id = result_ids[i];
+		}
+
 		lwfree(result_ids);
 		UF_destroy(uf);
 	}
 
-	if (context->cluster_assignments[row].is_null)
+	if (context->clusters[row].is_null)
 		PG_RETURN_NULL();
 
-	PG_RETURN_INT32(context->cluster_assignments[row].cluster_id);
+	PG_RETURN_INT32(context->clusters[row].cluster_id);
 }
 
+
 PG_FUNCTION_INFO_V1(ST_ClusterKMeans);
 Datum ST_ClusterKMeans(PG_FUNCTION_ARGS)
 {
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index ad4477760..082201cf9 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -1883,6 +1883,20 @@ CREATE OR REPLACE FUNCTION ST_ClusterDBSCAN (geometry, eps float8, minpoints int
 	LANGUAGE 'c' IMMUTABLE STRICT WINDOW PARALLEL SAFE
 	_COST_HIGH;
 
+-- Availability: 3.4.0
+CREATE OR REPLACE FUNCTION ST_ClusterWithinWin(geometry, distance float8)
+	RETURNS int
+	AS 'MODULE_PATHNAME', 'ST_ClusterWithinWin'
+	LANGUAGE 'c' IMMUTABLE STRICT WINDOW PARALLEL SAFE
+	_COST_HIGH;
+
+-- Availability: 3.4.0
+CREATE OR REPLACE FUNCTION ST_ClusterIntersectingWin(geometry)
+	RETURNS int
+	AS 'MODULE_PATHNAME', 'ST_ClusterIntersectingWin'
+	LANGUAGE 'c' IMMUTABLE STRICT WINDOW PARALLEL SAFE
+	_COST_HIGH;
+
 -- Availability: 1.2.2
 CREATE OR REPLACE FUNCTION ST_LineMerge(geometry)
 	RETURNS geometry
diff --git a/regress/core/cluster.sql b/regress/core/cluster.sql
index 7d2c61861..55a548440 100644
--- a/regress/core/cluster.sql
+++ b/regress/core/cluster.sql
@@ -15,6 +15,14 @@ SELECT 't2', ST_AsText(unnest(ST_ClusterIntersecting(array_agg(geom ORDER BY id)
 SELECT 't3', ST_AsText(unnest(ST_ClusterWithin(geom, 1.4 ORDER BY id))) FROM cluster_inputs;
 SELECT 't4', ST_AsText(unnest(ST_ClusterWithin(array_agg(geom ORDER BY id), 1.5))) FROM cluster_inputs;
 
+SELECT 't5', id, ST_AsText(geom), 
+  ST_ClusterWithinWin(geom, 1.4) OVER () AS cluster
+  FROM cluster_inputs;
+
+SELECT 't6', id, ST_AsText(geom), 
+  ST_ClusterIntersectingWin(geom) OVER () AS cluster
+  FROM cluster_inputs;
+
 -- tests for ST_DBSCAN
 
 CREATE TEMPORARY TABLE dbscan_inputs (id int, geom geometry);
diff --git a/regress/core/cluster_expected b/regress/core/cluster_expected
index 46390c9ec..72501347c 100644
--- a/regress/core/cluster_expected
+++ b/regress/core/cluster_expected
@@ -9,6 +9,20 @@ t3|GEOMETRYCOLLECTION(LINESTRING(6 6,7 7))
 t3|GEOMETRYCOLLECTION(POLYGON EMPTY)
 t4|GEOMETRYCOLLECTION(LINESTRING(0 0,1 1),LINESTRING(5 5,4 4),LINESTRING(0 0,-1 -1),LINESTRING(6 6,7 7),POLYGON((0 0,4 0,4 4,0 4,0 0)))
 t4|GEOMETRYCOLLECTION(POLYGON EMPTY)
+t5|1|LINESTRING(0 0,1 1)|0
+t5|2|LINESTRING(5 5,4 4)|0
+t5|3||
+t5|4|LINESTRING(0 0,-1 -1)|0
+t5|5|LINESTRING(6 6,7 7)|2
+t5|6|POLYGON EMPTY|3
+t5|7|POLYGON((0 0,4 0,4 4,0 4,0 0))|0
+t6|1|LINESTRING(0 0,1 1)|0
+t6|2|LINESTRING(5 5,4 4)|0
+t6|3||
+t6|4|LINESTRING(0 0,-1 -1)|0
+t6|5|LINESTRING(6 6,7 7)|2
+t6|6|POLYGON EMPTY|3
+t6|7|POLYGON((0 0,4 0,4 4,0 4,0 0))|0
 t101|1|0
 t101|2|0
 t101|3|0

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

Summary of changes:
 .gitignore                      |   1 +
 NEWS                            |   1 +
 doc/reference_cluster.xml       | 158 +++++++++++++++++++++++++++++--
 liblwgeom/lwgeom_geos.h         |   1 +
 liblwgeom/lwgeom_geos_cluster.c |   3 +-
 postgis/lwgeom_window.c         | 199 ++++++++++++++++++++++++++++++++++++++--
 postgis/postgis.sql.in          |  14 +++
 regress/core/cluster.sql        |   8 ++
 regress/core/cluster_expected   |  14 +++
 9 files changed, 381 insertions(+), 18 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list