<?xml version="1.0" encoding="UTF-8"?>
  <sect1 id="Clustering_Functions">
    <sect1info>
    <abstract>
    <para>These functions implement clustering algorithms for sets of geometries.</para>
    </abstract>
    </sect1info>
	<title>Clustering Functions</title>

    <refentry id="ST_ClusterDBSCAN">
	  <refnamediv>
		<refname>ST_ClusterDBSCAN</refname>

        <refpurpose>Window function that returns a cluster id for each input geometry using the DBSCAN algorithm.</refpurpose>
    </refnamediv>

	  <refsynopsisdiv>
		<funcsynopsis>
		  <funcprototype>
			<funcdef>integer <function>ST_ClusterDBSCAN</function></funcdef>

			<paramdef><type>geometry winset </type>
			<parameter>geom</parameter></paramdef>

			<paramdef><type>float8 </type>
			<parameter>eps</parameter></paramdef>

			<paramdef><type>integer </type>
			<parameter>minpoints</parameter></paramdef>
		  </funcprototype>
		</funcsynopsis>
	  </refsynopsisdiv>

	  <refsection>
      <title>Description</title>

	  <para>
		  Returns cluster number for each input geometry, based on a 2D implementation of the
          <ulink url="https://en.wikipedia.org/wiki/DBSCAN">Density-based spatial clustering of applications with noise (DBSCAN)</ulink>
		  algorithm.  Unlike <xref linkend="ST_ClusterKMeans" />, it does not require the number of clusters to be specified, but instead
		  uses the desired <link linkend="ST_Distance">distance</link> (<varname>eps</varname>) and density (<varname>minpoints</varname>) parameters to construct each cluster.
	  </para>

	  <para>
		  An input geometry will be added to a cluster if it is either:
		  <itemizedlist>
              <listitem>
                  <para>
                      A "core" geometry, that is within <varname>eps</varname> <link linkend="ST_Distance">distance</link> of at least <varname>minpoints</varname> input geometries (including itself) or
                  </para>
			  </listitem>
			  <listitem>
                  <para>
                      A "border" geometry, that is within <varname>eps</varname> <link linkend="ST_Distance">distance</link> of a core geometry.
                  </para>
			  </listitem>
		  </itemizedlist>
		</para>

		<para>
		  Note that border geometries may be within <varname>eps</varname> distance of core geometries in more than one cluster; in this
		  case, either assignment would be correct, and the border geometry will be arbitrarily asssigned to one of the available clusters.
		  In these cases, it is possible for a correct cluster to be generated with fewer than <varname>minpoints</varname> geometries.
		  When assignment of a border geometry is ambiguous, repeated calls to ST_ClusterDBSCAN will produce identical results if an ORDER BY
		  clause is included in the window definition, but cluster assignments may differ from other implementations of the same algorithm.
	  </para>

	  <note><para>
		  Input geometries that do not meet the criteria to join any other cluster will be assigned a cluster number of NULL.
	  </para></note>

      <para>Availability: 2.3.0</para>
    </refsection>

    <refsection>
      <title>Examples</title>
      <para>
          Assigning a cluster number to each polygon within 50 meters of each other.  Require at least 2 polygons per cluster
      </para>
	<informaltable>
				  <tgroup cols="2">
					<tbody>
				  <row>
						<entry><para><informalfigure>
							<mediaobject>
							  <imageobject>
								<imagedata fileref="images/st_clusterdbscan01.png" />
							  </imageobject>
							  <caption><para>within 50 meters at least 2 per cluster. singletons have NULL for cid</para></caption>
							</mediaobject>
						  </informalfigure>
  <programlisting>SELECT name, ST_ClusterDBSCAN(geom, eps := 50, minpoints := 2) over () AS cid
FROM boston_polys
WHERE name > '' AND building > ''
	AND ST_DWithin(geom,
        ST_Transform(
            ST_GeomFromText('POINT(-71.04054 42.35141)', 4326), 26986),
           500);</programlisting>
						  </para></entry>

						<entry><para><screen><![CDATA[                name                 | bucket
-------------------------------------+--------
 Manulife Tower                      |      0
 Park Lane Seaport I                 |      0
 Park Lane Seaport II                |      0
 Renaissance Boston Waterfront Hotel |      0
 Seaport Boston Hotel                |      0
 Seaport Hotel & World Trade Center  |      0
 Waterside Place                     |      0
 World Trade Center East             |      0
 100 Northern Avenue                 |      1
 100 Pier 4                          |      1
 The Institute of Contemporary Art   |      1
 101 Seaport                         |      2
 District Hall                       |      2
 One Marina Park Drive               |      2
 Twenty Two Liberty                  |      2
 Vertex                              |      2
 Vertex                              |      2
 Watermark Seaport                   |      2
 Blue Hills Bank Pavilion            |   NULL
 World Trade Center West             |   NULL
(20 rows)]]></screen></para>
				</entry>
					  </row>
				</tbody>
				</tgroup>
			</informaltable>


        <para>
            Combining parcels with the same cluster number into a single geometry. This uses named argument calling
        </para>
		    <programlisting>
SELECT cid, ST_Collect(geom) AS cluster_geom, array_agg(parcel_id) AS ids_in_cluster FROM (
    SELECT parcel_id, ST_ClusterDBSCAN(geom, eps := 0.5, minpoints := 5) over () AS cid, geom
    FROM parcels) sq
GROUP BY cid;
    </programlisting>
    </refsection>

    <refsection>
		  <title>See Also</title>
          <para><xref linkend="ST_DWithin"/>,
              <xref linkend="ST_ClusterKMeans"/>,
              <xref linkend="ST_ClusterIntersecting"/>,
              <xref linkend="ST_ClusterWithin"/>
          </para>
	  </refsection>

    </refentry>

    <refentry id="ST_ClusterIntersecting">
      <refnamediv>
        <refname>ST_ClusterIntersecting</refname>

        <refpurpose>Aggregate function that clusters the input geometries into connected sets.</refpurpose>
      </refnamediv>

      <refsynopsisdiv>
        <funcsynopsis>
          <funcprototype>
            <funcdef>geometry[] <function>ST_ClusterIntersecting</function></funcdef>
            <paramdef><type>geometry set</type> <parameter>g</parameter></paramdef>
          </funcprototype>
        </funcsynopsis>
      </refsynopsisdiv>

      <refsection>
        <title>Description</title>

        <para>ST_ClusterIntersecting is an aggregate function that returns an array of GeometryCollections, where each GeometryCollection represents an interconnected set of geometries.</para>

        <para>Availability: 2.2.0</para>
      </refsection>

      <refsection>
        <title>Examples</title>
        <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)

SELECT ST_AsText(unnest(ST_ClusterIntersecting(geom))) FROM testdata;

--result

st_astext
---------
GEOMETRYCOLLECTION(LINESTRING(0 0,1 1),LINESTRING(5 5,4 4),LINESTRING(0 0,-1 -1),POLYGON((0 0,4 0,4 4,0 4,0 0)))
GEOMETRYCOLLECTION(LINESTRING(6 6,7 7))
        </programlisting>
      </refsection>
      <refsection>
        <title>See Also</title>
        <para>
            <xref linkend="ST_ClusterDBSCAN" />,
            <xref linkend="ST_ClusterKMeans" />,
            <xref linkend="ST_ClusterWithin" />
        </para>
      </refsection>

    </refentry>


	<refentry id="ST_ClusterKMeans">
	  <refnamediv>
		<refname>ST_ClusterKMeans</refname>

		<refpurpose>Window function that returns a cluster id for each input geometry using the K-means algorithm.</refpurpose>
	  </refnamediv>

	  <refsynopsisdiv>
		<funcsynopsis>
		  <funcprototype>
			<funcdef>integer <function>ST_ClusterKMeans</function></funcdef>

			<paramdef><type>geometry winset </type>
			<parameter>geom</parameter></paramdef>

			<paramdef><type>integer </type>
			<parameter>number_of_clusters</parameter></paramdef>
		  </funcprototype>
		</funcsynopsis>
	  </refsynopsisdiv>

	  <refsection>
      <title>Description</title>

      <para>Returns 2D distance based
        <ulink url="https://en.wikipedia.org/wiki/K-means_clustering">K-means</ulink>
        cluster number for each input geometry. The distance used for clustering is the
        distance between the centroids of the geometries.
      </para>
      <para>Availability: 2.3.0</para>
    </refsection>

    <refsection>
      <title>Examples</title>
		<para>Generate dummy set of parcels for examples</para>
		<programlisting>CREATE TABLE parcels AS
SELECT lpad((row_number() over())::text,3,'0') As parcel_id, geom,
('{residential, commercial}'::text[])[1 + mod(row_number()OVER(),2)] As type
FROM
    ST_Subdivide(ST_Buffer('LINESTRING(40 100, 98 100, 100 150, 60 90)'::geometry,
    40, 'endcap=square'),12) As geom;
</programlisting>

	<informaltable>
			  <tgroup cols="1">
				<tbody>
				  <row>
					<entry>
						<para><informalfigure>
						<mediaobject>
						  <imageobject>
							<imagedata fileref="images/st_clusterkmeans01.png" />
						  </imageobject>
						  <caption><para>Original Parcels</para></caption>
						</mediaobject>
						</informalfigure>
						</para>
					</entry>
					<entry><para><informalfigure>
						<mediaobject>
						  <imageobject>
							<imagedata fileref="images/st_clusterkmeans02.png" />
						  </imageobject>
						  <caption><para>Parcels color-coded by cluster number (cid)</para></caption>
						</mediaobject>
					  </informalfigure>
						<programlisting>SELECT ST_ClusterKMeans(geom, 5) OVER() AS cid, parcel_id, geom
FROM parcels;
-- result
 cid | parcel_id |   geom
-----+-----------+---------------
   0 | 001       | 0103000000...
   0 | 002       | 0103000000...
   1 | 003       | 0103000000...
   0 | 004       | 0103000000...
   1 | 005       | 0103000000...
   2 | 006       | 0103000000...
   2 | 007       | 0103000000...
(7 rows)</programlisting>
					</para></entry>
				  </row>
			</tbody>
			</tgroup>
		</informaltable>

		    <programlisting> -- Partitioning parcel clusters by type
SELECT ST_ClusterKMeans(geom,3) over (PARTITION BY type) AS cid, parcel_id, type
FROM parcels;
-- result
 cid | parcel_id |    type
-----+-----------+-------------
   1 | 005       | commercial
   1 | 003       | commercial
   2 | 007       | commercial
   0 | 001       | commercial
   1 | 004       | residential
   0 | 002       | residential
   2 | 006       | residential
(7 rows)</programlisting>

    </refsection>

    <refsection>
		  <title>See Also</title>
          <para>
              <xref linkend="ST_ClusterDBSCAN"/>,
              <xref linkend="ST_ClusterIntersecting" />,
              <xref linkend="ST_ClusterWithin" />, <xref linkend="ST_Subdivide" />
          </para>
	  </refsection>
	</refentry>

	<refentry id="ST_ClusterWithin">
      <refnamediv>
        <refname>ST_ClusterWithin</refname>

        <refpurpose>Aggregate function that clusters the input geometries by separation distance.</refpurpose>
      </refnamediv>

      <refsynopsisdiv>
        <funcsynopsis>
          <funcprototype>
            <funcdef>geometry[] <function>ST_ClusterWithin</function></funcdef>
            <paramdef><type>geometry set </type> <parameter>g</parameter></paramdef>
            <paramdef><type>float8 </type> <parameter>distance</parameter></paramdef>
          </funcprototype>
        </funcsynopsis>
      </refsynopsisdiv>

      <refsection>
        <title>Description</title>

        <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>
      </refsection>

      <refsection>
        <title>Examples</title>
        <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)

SELECT ST_AsText(unnest(ST_ClusterWithin(geom, 1.4))) FROM testdata;

--result

st_astext
---------
GEOMETRYCOLLECTION(LINESTRING(0 0,1 1),LINESTRING(5 5,4 4),LINESTRING(0 0,-1 -1),POLYGON((0 0,4 0,4 4,0 4,0 0)))
GEOMETRYCOLLECTION(LINESTRING(6 6,7 7))
        </programlisting>
      </refsection>
      <refsection>
        <title>See Also</title>
        <para>
          <xref linkend="ST_ClusterDBSCAN" />,
          <xref linkend="ST_ClusterKMeans" />,
          <xref linkend="ST_ClusterIntersecting" />
        </para>
      </refsection>

    </refentry>

</sect1>
