PostGISを使用した空間クラスタリング?


97

私は、ポイントフィーチャ用にPostGIS対応データベース内で使用する空間クラスタリングアルゴリズムを探しています。入力として同じクラスター内のポイント間の距離を取得するplpgsql関数を作成します。出力では、クラスターの配列を返します。最も明らかな解決策は、フィーチャの周囲に指定された距離のバッファゾーンを構築し、このバッファ内でフィーチャを検索することです。そのような機能が存在する場合、それらの周囲にバッファの構築などを続けます。そのような機能が存在しない場合、クラスタ構築が完了したことを意味します。たぶん巧妙な解決策がありますか?


4
データの性質とクラスタリングの目的が異なるため、非常に多様なクラスタリング方法があります。そこにあるものの概要と、距離行列をクラスター化するために他の人がしていることについての簡単な読み物については、CV @ SEサイトを検索してください 実際、「クラスタリング方法の選択」はほぼ正確に同じであり、適切な答えがあります。
whuberの

8
代わりに、アルゴリズムへのリンクの実際のPostGISのSQLの例を見つけるための質問への+1は、特にのような、よりエキゾチックなクラスタリングのために、基本的なグリッド・クラスタリング以外の何のためのミッションは不可能であるMCL
wildpeaks

回答:


112

:PostGISのための少なくとも二つの良好なクラスタリング方法があるk個(VIA -means kmeans-postgresql閾値距離内の拡張)またはクラスターの幾何学的形状(PostGISの2.2)


1)k-とはkmeans-postgresql

インストール: POSIXホストシステムにPostgreSQL 8.4以上が必要です(MS Windowsのどこから始めればいいのかわかりません)。これをパッケージからインストールしている場合は、開発パッケージ(postgresql-develCentOSなど)も持っていることを確認してください。ダウンロードして解凍します:

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/

ビルドする前に、USE_PGXS 環境変数を設定する必要があります(以前の投稿ではMakefile、のこの部分を削除するように指示されていましたが、これは最良のオプションではありませんでした)。これらの2つのコマンドのいずれかがUnixシェルで機能するはずです。

# bash
export USE_PGXS=1
# csh
setenv USE_PGXS 1

次に、拡張機能をビルドしてインストールします。

make
make install
psql -f /usr/share/pgsql/contrib/kmeans.sql -U postgres -D postgis

(注:Ubuntu 10.10でもこれを試しましたpg_config --pgxsが、パスが存在しないので運がありません!これはおそらくUbuntuパッケージングのバグです)

使用法/例:ポイントのテーブルがどこかにあるはずです(QGISで多数の疑似ランダムポイントを描画しました)。ここに私がやったことの例があります:

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;

ウィンドウ関数の52番目の引数で指定したI kmeansは、5つのクラスターを生成するためのK整数です。これを任意の整数に変更できます。

以下に、私が描いた31個の疑似ランダムポイントと、各クラスターのカウントを示すラベルが付いた5つの重心を示します。これは、上記のSQLクエリを使用して作成されました。

Kmeans


また、ST_MinimumBoundingCircleを使用してこれらのクラスターがどこにあるかを示すこともできます。

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;

Kmeans2


2)しきい値距離内のクラスタリング ST_ClusterWithin

この集約関数はPostGIS 2.2に含まれており、すべてのコンポーネントが互いに距離内にあるGeometryCollectionの配列を返します。

以下に使用例を示します。100.0の距離が、5つの異なるクラスターをもたらすしきい値です。

SELECT row_number() over () AS id,
  ST_NumGeometries(gc),
  gc AS geom_collection,
  ST_Centroid(gc) AS centroid,
  ST_MinimumBoundingCircle(gc) AS circle,
  sqrt(ST_Area(ST_MinimumBoundingCircle(gc)) / pi()) AS radius
FROM (
  SELECT unnest(ST_ClusterWithin(geom, 100)) gc
  FROM rand_point
) f;

ClusterWithin100

最大の中央クラスターには、65.3単位または約130の囲み半径があり、これはしきい値よりも大きくなっています。これは、メンバージオメトリ間の個々の距離がしきい値よりも小さいため、1つの大きなクラスターとして結び付けられるためです。


2
素晴らしい、これらの変更はインストールに役立ちます:-)しかし、私は最後にその拡張機能を実際に使用できないのではないかと心配しています(正しく理解した場合)事前に微調整することはできますが、最後の画像の10ポイントクラスターの大きなギャップなど、任意の(さまざまなフィルターによる)データセットのクラスター化には適していません。ただし、これは他の人にも役立ちます(afaik)、これはその拡張機能の唯一の既存のSQLの例です(拡張機能のホームページにある1つのライナーを除く)。
ワイルドピーク

(ああ、あなたは私がそれを再公式化する以前のコメントを削除し、同時に答えた、申し訳ありませんが)
wildpeaks

7
kmeansクラスタリングの場合、事前にクラスターの数を指定する必要があります。ただし、クラスターの数が不要な代替アルゴリズムがある場合は興味があります。
djq

1
バージョン1.1.0が利用可能になりました:api.pgxn.org/dist/kmeans/1.1.0/kmeans-1.1.0.zip
djq

1
@maxd no。A =πr²の場合、r =√(A /π)。
マイクT

27

機能間の距離に基づいて機能のクラスターを計算し、この機能の上に凸包を構築する関数を作成しました。

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-クラスター距離。

この関数を使用した結果:

ここに画像の説明を入力してください


すばらしいです!関数を使用する方法の例を追加してもらえますか?ありがとう!
暗闇

1
ソースコードを少し修正し、関数の使用例を追加しました。
drnextgis

これをpostgres 9.1と "FOR innr IN EXECUTE 'SELECT' || gid ||"行で使用してみました AS gid、 '|| geom ||' as geom FROM tmp WHERE dmn IS NULL 'LOOP "では、次のエラーが発生します。何か案は ?エラー:セットを受け入れることができないコンテキストで呼び出されたセット値関数
ビットボックス

テーブルのPG(PostGIS n00b)でこのコードを使用する方法がわかりません。この構文をどこから理解できますか?私はクラスタ化するラッツとロンとテーブル持っている
MGA

まず、geometrylonlatを個別に保存するのではなく、テーブル内に列を作成し、一意の値(ID)で列を作成する必要があります。
drnextgis

10

これまでのところ、私が見つけた最も有望なのは、ウィンドウ関数としての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
;

2

@MikeTの回答を補完しています...

MS Windowsの場合:

要件:

あなたがすること:

  • ソースコードを微調整して、kmeans関数をDLLにエクスポートします。
  • 関数cl.exeを使用してDLLを生成するには、コンパイラでソースコードをコンパイルしkmeansます。
  • 生成されたDLLをPostgreSQL \ libフォルダーに入れます。
  • 次に、SQLコマンドを使用してUDFをPostgreSQLに「作成」(リンク)できます。

手順:

  1. 要件のダウンロードとインストール/抽出。
  2. kmeans.c任意のエディターで開きます:

    1. #include行の後、DLLEXPORTマクロを次のように定義します。

      #if defined(_WIN32)
          #define DLLEXPORT __declspec(dllexport)
      #else
         #define DLLEXPORT
      #endif
    2. DLLEXPORTこれらの各行の前に置きます:

      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);
  3. Visual C ++コマンドラインを開きます。

  4. コマンドラインで:

    1. 抽出されたに移動しkmeans-postgresqlます。
    2. POSTGRESPATHを設定します。たとえば、次のとおりです。 SET POSTGRESPATH=C:\Program Files\PostgreSQL\9.5
    3. 走る

      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"
  5. をコピーkmeans.dllする%POSTGRESPATH%\lib

  6. 次に、データベースでSQLコマンドを実行して、関数を「作成」します。

    CREATE FUNCTION kmeans(float[], int) RETURNS int
    AS '$libdir/kmeans'
    LANGUAGE c VOLATILE STRICT WINDOW;
    
    CREATE FUNCTION kmeans(float[], int, float[]) RETURNS int
    AS '$libdir/kmeans', 'kmeans_with_init'
    LANGUAGE C IMMUTABLE STRICT WINDOW;

2

このanwserで2)で指定されたPostGISクエリの結果をQGISで表示する方法を次に示します。

QGISは同じジオメトリ列でジオメトリコレクションも異なるデータ型も処理しないため、クラスター用とクラスター化ポイント用の2つのレイヤーを作成しました。

最初にクラスタの場合、必要なのはポリゴンのみで、他の結果は孤独なポイントです:

SELECT id,countfeature,circle FROM (SELECT row_number() over () AS id,
  ST_NumGeometries(gc) as countfeature,
  ST_MinimumBoundingCircle(gc) AS circle
FROM (
  SELECT unnest(ST_ClusterWithin(the_geom, 100)) gc
  FROM rand_point
) f) a WHERE ST_GeometryType(circle) = 'ST_Polygon'

次に、クラスタ化されたポイントの場合、ジオメトリコレクションをマルチポイントに変換する必要があります。

SELECT row_number() over () AS id,
  ST_NumGeometries(gc) as countfeature,
  ST_CollectionExtract(gc,1) AS multipoint
FROM (
  SELECT unnest(ST_ClusterWithin(the_geom, 100)) gc
  FROM rand_point
) f

一部のポイントは同じ座標にあるため、ラベルが混乱する可能性があります。

QGISのクラスタリング


2

2.3のpostgisで利用可能なST_ClusterKMeansメソッドを使用すると、Kmeansソリューションをより簡単に使用できます。例:

SELECT kmean, count(*), ST_SetSRID(ST_Extent(geom), 4326) as bbox 
FROM
(
    SELECT ST_ClusterKMeans(geom, 20) OVER() AS kmean, ST_Centroid(geom) as geom
    FROM sls_product 
) tsub
GROUP BY kmean;

上記の例では、フィーチャの境界ボックスがクラスタージオメトリとして使用されています。最初の画像は元のジオメトリを示し、2番目の画像は上記のselectの結果です。

元の形状 機能クラスター


1

ボトムアップクラスタリングソリューションからの動的なクエリを含まないpostgisの最大直径を持つポイントクラウドから単一クラスター取得します

CREATE TYPE pt AS (
    gid character varying(32),
    the_geom geometry(Point))

およびクラスターIDを持つタイプ

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

使用法:

WITH subq AS(
    SELECT ARRAY_AGG((gid, the_geom)::pt) AS points
    FROM data
    GROUP BY collection_id)
SELECT (clusters).* FROM 
    (SELECT buc(points, radius) AS clusters FROM subq
) y;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.