# Clustering Functions

ST_ClusterDBSCAN

Window function that returns a cluster id for each input geometry using the DBSCAN algorithm.

    			integer ST_ClusterDBSCAN

    			geometry winset
    			geom

    			float8
    			eps

    			integer
    			minpoints

### Description

A window function that returns a cluster number for each input geometry, using the 2D Density-based spatial clustering of applications with noise (DBSCAN) algorithm. Unlike ST_ClusterKMeans, it does not require the number of clusters to be specified, but instead uses the desired distance (`eps`) and density (`minpoints`) parameters to determine each cluster.

An input geometry is added to a cluster if it is either:

- A "core" geometry, that is within `eps` distance of at least `minpoints` input geometries (including itself); or

- A "border" geometry, that is within `eps` distance of a core geometry.

Note that border geometries may be within `eps` distance of core geometries in more than one cluster. Either assignment would be correct, so the border geometry will be arbitrarily assigned to one of the available clusters. In this situation it is possible for a correct cluster to be generated with fewer than `minpoints` geometries. To ensure deterministic assignment of border geometries (so that repeated calls to ST_ClusterDBSCAN will produce identical results) use an `ORDER BY` clause in the window definition. Ambiguous cluster assignments may differ from other DBSCAN implementations.

Geometries that do not meet the criteria to join any cluster are assigned a cluster number of NULL.

Availability: 2.3.0

&curve_support;

### Examples

Cluster polygons within 50 units of each other, requiring at least two polygons per cluster. The isolated polygon is reported as noise.

    WITH input(name, geom) AS (
      VALUES
        ('A1', ST_MakeEnvelope(0, 0, 10, 10)),
        ('A2', ST_MakeEnvelope(30, 0, 40, 10)),
        ('B1', ST_MakeEnvelope(100, 0, 110, 10)),
        ('B2', ST_MakeEnvelope(130, 0, 140, 10)),
        ('noise', ST_MakeEnvelope(250, 0, 260, 10))
    ), clustered AS (
      SELECT name,
             geom,
             ST_ClusterDBSCAN(geom, eps => 50, minpoints => 2)
               OVER (ORDER BY name) AS cid
      FROM input
    )
    SELECT COALESCE(cid::text, 'noise') AS cluster,
           string_agg(name, ',' ORDER BY name) AS members,
           ST_Collect(geom ORDER BY name) AS cluster_geom
    FROM clustered
    GROUP BY cid
    ORDER BY cid NULLS LAST;

    0 | A1,A2 | MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0)),((30 0,30 10,40 10,40 0,30 0)))
    1 | B1,B2 | MULTIPOLYGON(((100 0,100 10,110 10,110 0,100 0)),((130 0,130 10,140 10,140 0,130 0)))
    noise | noise | MULTIPOLYGON(((250 0,250 10,260 10,260 0,250 0)))

A example showing combining parcels with the same cluster number into geometry collections.

    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;

### See Also

ST_DWithin, ST_ClusterKMeans, ST_ClusterIntersecting, ST_ClusterIntersectingWin, ST_ClusterWithin, ST_ClusterWithinWin

ST_ClusterIntersecting

Aggregate function that clusters input geometries into connected sets.

                geometry[] ST_ClusterIntersecting
                geometry set g

### Description

An aggregate function that returns an array of GeometryCollections partitioning the input geometries into connected clusters that are disjoint. Each geometry in a cluster intersects at least one other geometry in the cluster, and does not intersect any geometry in other clusters.

Availability: 2.2.0

### Examples

    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 unnest(ST_ClusterIntersecting(geom)) FROM testdata;

    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))

### See Also

ST_ClusterIntersectingWin, ST_ClusterWithin, ST_ClusterWithinWin

ST_ClusterIntersectingWin

Window function that returns a cluster id for each input geometry, clustering input geometries into connected sets.

                integer ST_ClusterIntersectingWin
                geometry winset  geom

### Description

A window function that builds connected 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.

Availability: 3.4.0

### Examples

Return an explicit cluster id for each input geometry. Using the conventional `cid` alias keeps the geometry column ready for color-by-cluster rendering.

    WITH testdata(id, geom) AS (
      VALUES (1, 'LINESTRING (0 0,1 1)'::geometry),
             (2, 'LINESTRING (5 5,4 4)'::geometry),
             (3, 'LINESTRING (6 6,7 7)'::geometry),
             (4, 'LINESTRING (0 0,-1 -1)'::geometry),
             (5, 'POLYGON ((0 0,4 0,4 4,0 4,0 0))'::geometry)
    ), clustered AS (
      SELECT id,
             geom,
             ST_ClusterIntersectingWin(geom) OVER () AS cid
      FROM testdata
    )
    SELECT id,
           geom AS geom_wkt,
           cid
    FROM clustered
    ORDER BY id;

     id |            geom_wkt            | cid
    ----+--------------------------------+-----
      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

### See Also

ST_ClusterIntersecting, ST_ClusterWithin, ST_ClusterWithinWin

ST_ClusterRelateWin

Window function that returns a cluster id for each input geometry, clustering input geometries into connected sets using the relate pattern to determine whether the geometries are connected.

                integer ST_ClusterRelateWin
                geometry winset  geom
                text  relate_matrix

### Description

A window function that builds connected clusters of geometries that intersect. Geometries are added to a cluster if they share a pairwise DE9IM relationship with another member of the cluster. With this function it is possible to build a cluster of all objects that touch at boundaries, but exclude those that merely overlap.

Availability: 3.7.0

### Examples

This collection of line strings would form a single cluster using ST_ClusterIntersectingWin, but using ST_ClusterRelateWin can be clustered into three groups that connect only via their end points.

    WITH data(id, geom) AS (
      VALUES (1, 'LINESTRING(0 0,1 1)'::geometry),
             (2, 'LINESTRING(2 2,1 1)'::geometry),
             (3, 'LINESTRING(0 1,1 0)'::geometry),
             (4, 'LINESTRING(0 1,0 4)'::geometry),
             (5, 'LINESTRING(2 2,2 4)'::geometry),
             (6, 'LINESTRING(1.5 2.5,2.5 3.5)'::geometry)
    ), clustered AS (
      SELECT id, geom,
             ST_ClusterRelateWin(geom, '****0****') OVER () AS cluster
      FROM data
    )
    SELECT cluster,
           string_agg(id::text, ',' ORDER BY id) AS members,
           ST_Collect(geom ORDER BY id) AS cluster_geom
    FROM clustered
    GROUP BY cluster
    ORDER BY cluster;

    0 | 1,2,5 | MULTILINESTRING((0 0,1 1),(2 2,1 1),(2 2,2 4))
    1 | 3,4 | MULTILINESTRING((0 1,1 0),(0 1,0 4))
    2 | 6 | MULTILINESTRING((1.5 2.5,2.5 3.5))

### See Also

ST_Relate, ST_ClusterIntersectingWin, ST_ClusterWithinWin

ST_MinimumSpanningTree

Window function that returns a tree id for each input geometry, clustering input geometries into connected trees using the Minimum Spanning Tree algorithm.

                integer ST_MinimumSpanningTree
                geometry winset  geom

### Description

A window function that builds connected graphs of line strings based on the Minimum Spanning Tree (MST) of the input geometries. The return value is the cluster number that the geometry argument participates in, or zero if it is not part of the minimum tree.

The Minimum Spanning Tree connects all geometries in the window partition such that the total length of the connecting lines is minimized. If the graph is not fully connected (e.g. infinite distance between some geometries), it produces a Minimum Spanning Forest, and each connected component is assigned a unique tree ID.

Availability: 3.7.0

Requires GEOS >= 3.15.0

### Examples

These six edges describe a square with both diagonals. The minimum spanning tree keeps three edges and excludes the rest.

    WITH edges(id, geom) AS (
      VALUES
        (1, 'LINESTRING(0 0,1 0)'::geometry),
        (2, 'LINESTRING(0 0,0 1)'::geometry),
        (3, 'LINESTRING(1 1,0 1)'::geometry),
        (4, 'LINESTRING(1 1,1 0)'::geometry),
        (5, 'LINESTRING(0 0,1 1)'::geometry),
        (6, 'LINESTRING(1 0,0 1)'::geometry)
    ), marked AS (
      SELECT id,
             geom,
             ST_MinimumSpanningTree(geom) OVER (ORDER BY id) AS tree_id
      FROM edges
    )
    SELECT string_agg(id::text, ',' ORDER BY id)
             FILTER (WHERE tree_id > 0) AS tree_edge_ids,
           ST_Collect(geom ORDER BY id)
             FILTER (WHERE tree_id > 0) AS tree_edges,
           string_agg(id::text, ',' ORDER BY id)
             FILTER (WHERE tree_id = 0) AS excluded_edge_ids,
           ST_Collect(geom ORDER BY id)
             FILTER (WHERE tree_id = 0) AS excluded_edges
    FROM marked;

    -[ RECORD 1 ]-----
    tree_edge_ids     | 1,2,3
    tree_edges        | MULTILINESTRING((0 0,1 0),(0 0,0 1),(1 1,0 1))
    excluded_edge_ids | 4,5,6
    excluded_edges    | MULTILINESTRING((1 1,1 0),(0 0,1 1),(1 0,0 1))

### See Also

ST_ClusterDBSCAN, ST_ClusterKMeans, ST_ClusterIntersectingWin

ST_ClusterKMeans

Window function that returns a cluster id for each input geometry using the K-means algorithm.

                integer ST_ClusterKMeans

                  geometry winset
                  geom

                  integer
                  k

                  float8
                  max_radius

### Description

Returns K-means cluster number for each input geometry. The distance used for clustering is the distance between the centroids for 2D geometries, and distance between bounding box centers for 3D geometries. For POINT inputs, M coordinate will be treated as weight of input and has to be larger than 0.

`max_radius`, if set, will cause ST_ClusterKMeans to generate more clusters than `k` ensuring that no cluster in output has radius larger than `max_radius`. This is useful in reachability analysis.

Enhanced: 3.2.0 Support for `max_radius`

Enhanced: 3.1.0 Support for 3D geometries and weights

Availability: 2.3.0

### Examples

Define the parcel geometries once in a CTE and reuse them across the executable examples.

Parcels color-coded by cluster number (`cid`).

    WITH parcels(parcel_id, geom) AS (
      VALUES
        ('A1', ST_MakeEnvelope(0, 0, 10, 10)),
        ('A2', ST_MakeEnvelope(10, 0, 20, 10)),
        ('B1', ST_MakeEnvelope(100, 0, 110, 10)),
        ('B2', ST_MakeEnvelope(110, 0, 120, 10)),
        ('C1', ST_MakeEnvelope(200, 0, 210, 10)),
        ('C2', ST_MakeEnvelope(210, 0, 220, 10))
    ), clustered AS (
      SELECT parcel_id,
             geom,
             ST_ClusterKMeans(geom, 3) OVER (ORDER BY parcel_id) AS cid
      FROM parcels
    )
    SELECT cid,
           string_agg(parcel_id, ',' ORDER BY parcel_id) AS parcels,
           ST_Union(geom) AS cluster_geom
    FROM clustered
    GROUP BY cid
    ORDER BY cid;

    0 | C1,C2 | POLYGON((200 10,210 10,220 10,220 0,210 0,200 0,200 10))
    1 | A1,A2 | POLYGON((0 10,10 10,20 10,20 0,10 0,0 0,0 10))
    2 | B1,B2 | POLYGON((100 10,110 10,120 10,120 0,110 0,100 0,100 10))

Partitioning parcel clusters by type:

    WITH parcel_geoms AS (
      SELECT geom
      FROM ST_Subdivide(
        ST_Buffer('SRID=3857;LINESTRING(40 100,98 100,100 150,60 90)'::geometry,
                  40, 'endcap=square'),
        12) AS geom
    ), parcels AS (
      SELECT lpad(row_number() OVER (ORDER BY ST_YMin(geom), ST_XMin(geom))::text, 3, '0') AS parcel_id,
             geom,
             ('{residential,commercial}'::text[])[1 + mod(row_number() OVER (ORDER BY ST_YMin(geom), ST_XMin(geom)), 2)] AS type
      FROM parcel_geoms
    )
    SELECT ST_ClusterKMeans(geom, 3) over (PARTITION BY type) AS cid,
           parcel_id,
           type,
           ST_SnapToGrid(geom, 1) AS parcel_geom
    FROM parcels
    ORDER BY type, parcel_id;

     cid | parcel_id |    type     | parcel_geom
    -----+-----------+-------------+--------------------------------------------------------------
       0 | 001       | commercial  | POLYGON((33 60,0 60,0 98,71 98,71 35,33 60))
       2 | 003       | commercial  | POLYGON((106 98,138 98,137 91,134 84,131 77,126 71,120 66,113 63,106 61,106 98))
       2 | 005       | commercial  | POLYGON((140 148,138 98,72 98,72 148,140 148))
       1 | 007       | commercial  | POLYGON((109 189,116 187,123 183,129 178,134 171,137 164,139 156,140 148,109 148,109 189))
       0 | 002       | residential | POLYGON((71 98,106 98,106 61,98 60,88 60,71 35,71 98))
       2 | 004       | residential | POLYGON((0 98,0 140,45 140,67 172,72 178,72 98,0 98))
       1 | 006       | residential | POLYGON((72 178,78 183,85 187,93 189,101 190,109 189,109 148,72 148,72 178))

Example: Clustering a preaggregated planetary-scale data population dataset using 3D clustering and weighting. Identify at least 20 regions based on Kontur Population Data that do not span more than 3000 km from their center:

    CREATE TABLE kontur_population_3000km_clusters AS
    SELECT
        geom,
        ST_ClusterKMeans(
            ST_Force4D(
                ST_Transform(ST_Force3D(geom), 4978),
                mvalue => population
            ),
            20,
            max_radius => 3000000
        ) OVER () AS cid
    FROM kontur_population;

The clustering produces 46 regions. Clusters are centered at well-populated regions such as New York and Moscow. Greenland forms one cluster, several island clusters span the antimeridian, and cluster edges follow the Earth's curvature.

Kontur population clustered with 3000 km maximum radius.

Cluster weighted city populations on the Earth in geocentric coordinates. The maximum radius is specified in meters, while the returned geometries stay in WGS 84 for display.

    WITH population(place, population, geom) AS (
      VALUES
        ('Boston', 5.0, ST_SetSRID(ST_Point(-71.06, 42.36), 4326)),
        ('New York', 20.0, ST_SetSRID(ST_Point(-74.01, 40.71), 4326)),
        ('London', 15.0, ST_SetSRID(ST_Point(-0.13, 51.51), 4326)),
        ('Paris', 11.0, ST_SetSRID(ST_Point(2.35, 48.86), 4326)),
        ('Osaka', 19.0, ST_SetSRID(ST_Point(135.50, 34.69), 4326)),
        ('Tokyo', 37.0, ST_SetSRID(ST_Point(139.69, 35.68), 4326)),
        ('Melbourne', 5.0, ST_SetSRID(ST_Point(144.96, -37.81), 4326)),
        ('Sydney', 5.0, ST_SetSRID(ST_Point(151.21, -33.87), 4326))
    ), clustered AS (
      SELECT place,
             geom,
             ST_ClusterKMeans(
               ST_Force4D(
                 ST_Transform(ST_Force3D(geom), 4978),
                 mvalue => population
               ),
               4,
               max_radius => 3000000
             ) OVER (ORDER BY place) AS cid
      FROM population
    )
    SELECT cid,
           string_agg(place, ', ' ORDER BY place) AS places,
           ST_Collect(geom ORDER BY place) AS cluster_geom
    FROM clustered
    GROUP BY cid
    ORDER BY cid;

    0 | Melbourne, Sydney | MULTIPOINT((144.96 -37.81),(151.21 -33.87))
    1 | Boston, New York | MULTIPOINT((-71.06 42.36),(-74.01 40.71))
    2 | Osaka, Tokyo | MULTIPOINT((135.5 34.69),(139.69 35.68))
    3 | London, Paris | MULTIPOINT((-0.13 51.51),(2.35 48.86))

### See Also

ST_ClusterDBSCAN, ST_ClusterIntersectingWin, ST_ClusterWithinWin, ST_ClusterIntersecting, ST_ClusterWithin, ST_Subdivide, ST_Force_3D, ST_Force_4D,

ST_ClusterWithin

Aggregate function that clusters geometries by separation distance.

                geometry[] ST_ClusterWithin
                geometry set  g
                float8  distance

### Description

An aggregate function that returns an array of GeometryCollections, where each collection is a cluster containing some input geometries. Clustering partitions the input geometries into sets in which each geometry is within the specified `distance` of at least one other geometry in the same cluster. Distances are Cartesian distances in the units of the SRID.

ST_ClusterWithin is equivalent to running ST_ClusterDBSCAN with `minpoints => 0`.

Availability: 2.2.0

&curve_support;

### Examples

    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 unnest(ST_ClusterWithin(geom, 1.4)) FROM testdata;

    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))

### See Also

ST_ClusterWithinWin, ST_ClusterDBSCAN, ST_ClusterIntersecting, ST_ClusterIntersectingWin

ST_ClusterWithinWin

Window function that returns a cluster id for each input geometry, clustering using separation distance.

                integer ST_ClusterWithinWin
                geometry winset  geom
                float8  distance

### Description

A window function that returns a cluster number for each input geometry. Clustering partitions the geometries into sets in which each geometry is within the specified `distance` of at least one other geometry in the same cluster. Distances are Cartesian distances in the units of the SRID.

ST_ClusterWithinWin is equivalent to running ST_ClusterDBSCAN with `minpoints => 0`.

Availability: 3.4.0

&curve_support;

### Examples

    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,
      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

### See Also

ST_ClusterWithin, ST_ClusterDBSCAN, ST_ClusterIntersecting, ST_ClusterIntersectingWin,