我正在寻找空间聚类算法,以便在支持PostGIS的数据库中将其用于点要素。我将编写plpgsql函数,该函数将同一群集内的点之间的距离作为输入。在输出函数处返回集群数组。最明显的解决方案是在特征周围建立指定距离的缓冲区,然后在该缓冲区中搜索特征。如果存在此类功能,则继续围绕它们构建缓冲区,等等。如果不存在此类功能,则意味着集群构建已完成。也许有一些聪明的解决方案?

评论

由于数据的性质不同和群集的目的不同,因此群集方法种类繁多。要了解现有内容的概述,以及要轻松了解其他人如何对距离矩阵进行聚类,请搜索CV @ SE网站。实际上,“选择聚类方法”几乎与您完全相同,并且有很好的答案。

对问题+1,因为除了基本的网格聚类之外,对于其他任何事物,尤其是对于MCL等更奇特的聚类,要找到一个实际的PostGIS SQL示例而不是与算法的链接都是不可能的

#1 楼

PostGIS至少有两种良好的聚类方法:k均值(通过kmeans-postgresql扩展)或阈值距离内的聚类几何形状(PostGIS 2.2)


1)具有kmeans-postgresql


安装:您需要从源代码进行编译和安装,这在* NIX上比Windows容易(我不知道从哪里开始)。如果从软件包中安装了PostgreSQL,请确保您也具有开发软件包(例如,对于CentOS为postgresql-devel)。

下载,提取,构建和安装: />
wget http://api.pgxn.org/dist/kmeans/1.1.0/kmeans-1.1.0.zip
unzip kmeans-1.1.0.zip
cd kmeans-1.1.0/
make USE_PGXS=1
sudo make install


启用数据库中的扩展名(使用psql,pgAdmin等):


CREATE EXTENSION kmeans;


用法/示例:您应该在某处有一个点表(我在QGIS中绘制了一堆伪随机点)。以下是我所做的示例:集群。您可以将其更改为所需的任何整数。

下面是我绘制的31个伪随机点和五个质心,其中的标签显示每个群集的计数。这是使用上述SQL查询创建的。




您还可以尝试使用ST_MinimumBoundingCircle来说明这些群集的位置: >
SELECT kmeans, count(*), ST_Centroid(ST_Collect(geom)) AS geom
FROM (
  SELECT kmeans(ARRAY[ST_X(geom), ST_Y(geom)], 5) OVER (), geom
  FROM rand_point
) AS ksub
GROUP BY kmeans
ORDER BY kmeans;





2)使用5在阈值距离内聚类


此聚合函数包含在PostGIS 2.2中,并返回一个GeometryCollections数组,其中所有组件彼此之间的距离都在此范围内。

这里是一个示例用法,其中100.0的距离是导致5个不同的群集:

SELECT kmeans, ST_MinimumBoundingCircle(ST_Collect(geom)) AS circle
FROM (
  SELECT kmeans(ARRAY[ST_X(geom), ST_Y(geom)], 5) OVER (), geom
  FROM rand_point
) AS ksub
GROUP BY kmeans
ORDER BY kmeans;




最大的中间簇的包围圆半径为65.3单位或大约130,大于阈值。这是因为成员几何之间的单个距离小于阈值,因此将其绑定为一个较大的簇。

评论


太好了,这些修改将对安装有所帮助:-)但是,我担心我最终不能真正使用该扩展名,因为(如果我正确理解的话)它需要一个硬编码的不可思议的集群数,这可以解决静态数据问题您可以事先对其进行微调,但不适合将任意(由于各种过滤器)数据集聚类,例如最后一张图像上10点群集中的较大间隙。但是,这也会对其他人有所帮助,因为(afaik),这是该扩展程序的唯一现有SQL示例(该扩展程序首页上的一个衬里除外)。

–狂话
2011年7月4日,11:19



(啊,您同时回答了,我删除了以前的评论以重新制定它,对不起)

–狂话
2011年7月4日,11:20



对于kmeans集群,您需要预先指定集群的数量。我很好奇是否有其他不需要簇数的算法。

– djq
2011年7月10日15:09

版本1.1.0现在可用:api.pgxn.org/dist/kmeans/1.1.0/kmeans-1.1.0.zip

– djq
2012年8月26日在21:26

@maxd号给定A =πr²,则r =√(A /π)。

– Mike T
16年4月26日在20:57

#2 楼

我编写了一个函数,该函数根据特征之间的距离计算特征簇,并在此特征上构建凸包:

CREATE OR REPLACE FUNCTION get_domains_n(lname varchar, geom varchar, gid varchar, radius numeric)
    RETURNS SETOF record AS
$$
DECLARE
    lid_new    integer;
    dmn_number integer := 1;
    outr       record;
    innr       record;
    r          record;
BEGIN

    DROP TABLE IF EXISTS tmp;
    EXECUTE 'CREATE TEMPORARY TABLE tmp AS SELECT '||gid||', '||geom||' FROM '||lname;
    ALTER TABLE tmp ADD COLUMN dmn integer;
    ALTER TABLE tmp ADD COLUMN chk boolean DEFAULT FALSE;
    EXECUTE 'UPDATE tmp SET dmn = '||dmn_number||', chk = FALSE WHERE '||gid||' = (SELECT MIN('||gid||') FROM tmp)';

    LOOP
        LOOP
            FOR outr IN EXECUTE 'SELECT '||gid||' AS gid, '||geom||' AS geom FROM tmp WHERE dmn = '||dmn_number||' AND NOT chk' LOOP
                FOR innr IN EXECUTE 'SELECT '||gid||' AS gid, '||geom||' AS geom FROM tmp WHERE dmn IS NULL' LOOP
                    IF ST_DWithin(ST_Transform(ST_SetSRID(outr.geom, 4326), 3785), ST_Transform(ST_SetSRID(innr.geom, 4326), 3785), radius) THEN
                    --IF ST_DWithin(outr.geom, innr.geom, radius) THEN
                        EXECUTE 'UPDATE tmp SET dmn = '||dmn_number||', chk = FALSE WHERE '||gid||' = '||innr.gid;
                    END IF;
                END LOOP;
                EXECUTE 'UPDATE tmp SET chk = TRUE WHERE '||gid||' = '||outr.gid;
            END LOOP;
            SELECT INTO r dmn FROM tmp WHERE dmn = dmn_number AND NOT chk LIMIT 1;
            EXIT WHEN NOT FOUND;
       END LOOP;
       SELECT INTO r dmn FROM tmp WHERE dmn IS NULL LIMIT 1;
       IF FOUND THEN
           dmn_number := dmn_number + 1;
           EXECUTE 'UPDATE tmp SET dmn = '||dmn_number||', chk = FALSE WHERE '||gid||' = (SELECT MIN('||gid||') FROM tmp WHERE dmn IS NULL LIMIT 1)';
       ELSE
           EXIT;
       END IF;
    END LOOP;

    RETURN QUERY EXECUTE 'SELECT ST_ConvexHull(ST_Collect('||geom||')) FROM tmp GROUP by dmn';

    RETURN;
END
$$
LANGUAGE plpgsql;


使用此函数的示例:

SELECT * FROM get_domains_n('poi', 'wkb_geometry', 'ogc_fid', 14000) AS g(gm geometry)


'poi'-图层名称,'wkb_geometry'-几何列名称,'ogc_fid'-表的主键,14000-簇距离。

使用此功能的结果:



评论


大!您能否添加一个示例,说明如何使用您的函数?谢谢!

– Underdark♦
2011年7月5日在7:24

我修改了一点源代码,并添加了使用函数的示例。

–drnextgis
2011年7月5日在9:28

刚刚尝试在postgres 9.1和行“ FOR innr IN EXECUTE'SELECT'|| gid ||'中使用它作为提示,“ || geom ||”从tmp WHERE dmn IS NULL'LOOP“发出AS geom会产生以下错误。有任何想法吗 ?错误:在无法接受集合的上下文中调用的集合值函数

–位盒
2012年10月18日,11:11



我不确定如何在表的PG(PostGIS n00b)中使用此代码。我从哪里可以开始理解这种语法?我有一张桌子,上面有我想聚拢的经纬度

– mga
2014年5月5日20:41

首先,您必须在表中构建几何列,而不是单独存储lonlat并使列具有唯一值(ID)。

–drnextgis
2014年5月6日10:44



#3 楼

到目前为止,我发现最有前途的是作为窗口功能的K-means集群扩展:http://pgxn.org/dist/kmeans/



否则,对于基本的网格群集,您可以使用SnapToGrid。

SELECT
    array_agg(id) AS ids,
    COUNT( position ) AS count,
    ST_AsText( ST_Centroid(ST_Collect( position )) ) AS center,
FROM mytable
GROUP BY
    ST_SnapToGrid( ST_SetSRID(position, 4326), 22.25, 11.125)
ORDER BY
    count DESC
;


#4 楼

您可以通过2.3中的postgis中可用的ST_ClusterKMeans方法更轻松地使用Kmeans解决方案。示例:
第一个图像显示的是原始几何形状,第二个图像是上述选择的结果。


#5 楼

补充@MikeT答案...

对于MS Windows:

要求:


任何像这样的Visual C ++ Express版本

kmeans-postgresql模块。

您将做什么:


调整源代码以将kmeans函数导出到DLL。
使用cl.exe编译器编译源代码以使用kmeans函数生成DLL。
将生成的DLL放入PostgreSQL \ lib文件夹中。
然后您可以“创建”(链接)UDF到PostgreSQL通过SQL命令。

步骤:


下载并安装/提取要求。在kmeans.c行之后,使用以下命令定义DLLEXPORT宏:

#if defined(_WIN32)
    #define DLLEXPORT __declspec(dllexport)
#else
   #define DLLEXPORT
#endif



#include放在每一个之前行:

PG_FUNCTION_INFO_V1(kmeans_with_init);
PG_FUNCTION_INFO_V1(kmeans);

extern Datum kmeans_with_init(PG_FUNCTION_ARGS);
extern Datum kmeans(PG_FUNCTION_ARGS);




打开Visual C ++命令行。

在命令行中:


转到提取的DLLEXPORT
设置我的POSTGRESPATH例如:kmeans-postgresql


运行

cl.exe /I"%POSTGRESPATH%\include" /I"%POSTGRESPATH%\include\server" /I"%POSTGRESPATH%\include\server\port\win32" /I"%POSTGRESPATH%\include\server\port\win32_msvc" /I"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include" /LD kmeans.c "%POSTGRESPATH%\lib\postgres.lib"




SET POSTGRESPATH=C:\Program Files\PostgreSQL.5复制到kmeans.dll

现在在数据库中运行SQL命令以“创建”功能。


#6 楼

这是一种在QGIS中显示此anwser中2)中给出的PostGIS查询结果的方法。两层,一层用于聚类,一层用于聚类点。

首先用于聚类,您只需要多边形,其他结果是孤独的点:
/>然后,对于聚类点,您需要在多点中转换几何体集合: br />

#7 楼

自底向上的聚类解决方案,从postgis中从具有最大直径的点云中获取单个聚类,不涉及任何动态查询。 >
CREATE TYPE pt AS (
    gid character varying(32),
    the_geom geometry(Point))


下一个算法函数

CREATE TYPE clustered_pt AS (
    gid character varying(32),
    the_geom geometry(Point)
    cluster_id int)


用法:

CREATE OR REPLACE FUNCTION buc(points pt[], radius integer)
RETURNS SETOF clustered_pt AS
$BODY$

DECLARE
    srid int;
    joined_clusters int[];

BEGIN

--If there's only 1 point, don't bother with the loop.
IF array_length(points,1)<2 THEN
    RETURN QUERY SELECT gid, the_geom, 1 FROM unnest(points);
    RETURN;
END IF;

CREATE TEMPORARY TABLE IF NOT EXISTS points2 (LIKE pt) ON COMMIT DROP;

BEGIN
    ALTER TABLE points2 ADD COLUMN cluster_id serial;
EXCEPTION
    WHEN duplicate_column THEN --do nothing. Exception comes up when using this function multiple times
END;

TRUNCATE points2;
    --inserting points in
INSERT INTO points2(gid, the_geom)
    (SELECT (unnest(points)).* ); 

--Store the srid to reconvert points after, assumes all points have the same SRID
srid := ST_SRID(the_geom) FROM points2 LIMIT 1;

UPDATE points2 --transforming points to a UTM coordinate system so distances will be calculated in meters.
SET the_geom =  ST_TRANSFORM(the_geom,26986);

--Adding spatial index
CREATE INDEX points_index
ON points2
USING gist
(the_geom);

ANALYZE points2;

LOOP
    --If the smallest maximum distance between two clusters is greater than 2x the desired cluster radius, then there are no more clusters to be formed
    IF (SELECT ST_MaxDistance(ST_Collect(a.the_geom),ST_Collect(b.the_geom))  FROM points2 a, points2 b
        WHERE a.cluster_id <> b.cluster_id
        GROUP BY a.cluster_id, b.cluster_id 
        ORDER BY ST_MaxDistance(ST_Collect(a.the_geom),ST_Collect(b.the_geom)) LIMIT 1)
        > 2 * radius
    THEN
        EXIT;
    END IF;

    joined_clusters := ARRAY[a.cluster_id,b.cluster_id]
        FROM points2 a, points2 b
        WHERE a.cluster_id <> b.cluster_id
        GROUP BY a.cluster_id, b.cluster_id
        ORDER BY ST_MaxDistance(ST_Collect(a.the_geom),ST_Collect(b.the_geom)) 
        LIMIT 1;

    UPDATE points2
    SET cluster_id = joined_clusters[1]
    WHERE cluster_id = joined_clusters[2];

    --If there's only 1 cluster left, exit loop
    IF (SELECT COUNT(DISTINCT cluster_id) FROM points2) < 2 THEN
        EXIT;

    END IF;

END LOOP;

RETURN QUERY SELECT gid, ST_TRANSFORM(the_geom, srid)::geometry(point), cluster_id FROM points2;
END;
$BODY$
LANGUAGE plpgsql