PostGIS를 사용한 공간 클러스터링?


97

포인트 기능을 위해 PostGIS 지원 데이터베이스 내에서 사용할 공간 클러스터링 알고리즘을 찾고 있습니다. 입력과 같은 클러스터 내의 포인트 사이의 거리를 차지하는 plpgsql 함수를 작성하겠습니다. 출력 함수에서 클러스터 배열을 반환합니다. 가장 확실한 해결책은 피처 주위에 지정된 거리의 버퍼 영역을 구축하고이 버퍼에서 피처를 검색하는 것입니다. 이러한 기능이 존재하면 계속해서 주변에 버퍼를 작성하십시오. 이러한 기능이 존재하지 않으면 클러스터 빌드가 완료된 것입니다. 영리한 해결책이 있습니까?


4
데이터의 특성과 클러스터링의 목적이 다르기 때문에 다양한 클러스터링 방법이 있습니다. 무엇이 존재하는지에 대한 개요와 다른 사람들이 거리 행렬을 클러스터링하기 위해 무엇을하고 있는지에 대한 쉬운 정보를 얻으 려면 CV @ SE site를 검색하십시오 . 실제로 "클러스터링 방법 선택" 은 거의 동일한 복제 방법이며 정답이 있습니다.
whuber

8
질문에 +1 알고리즘 링크 대신 실제 PostGIS SQL 예제를 찾는 것은 기본 그리드 클러스터링 이외의 다른 것, 특히 MCL
wildpeaks

답변:


112

PostGIS에 대한 최소 두 가지 클러스터링 방법이 있습니다 : k- 평균 ( 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은 옵션 중 가장 좋지 않은 이 부분을 삭제하도록 지시했습니다 ). 다음 두 명령 중 하나가 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

(참고 : 우분투 10.10에서도 이것을 시도했지만 경로가 없기 때문에 운 pg_config --pgxs이 없습니다! 이것은 아마도 우분투 패키징 버그 일 것입니다)

사용법 / 예 : 어딘가에 포인트 테이블이 있어야합니다 (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;

window 함수 5의 두 번째 인수에서 제공 한 I 는 5 개의 클러스터를 생성 kmeans하는 K 정수입니다. 원하는 정수로 변경할 수 있습니다.

아래는 내가 그린 31 개의 의사 랜덤 포인트와 각 클러스터의 개수를 나타내는 레이블이있는 5 개의 중심입니다. 이것은 위의 SQL 쿼리를 사용하여 작성되었습니다.

켐스


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;

100 이내

가장 큰 중간 군집은 65.3 단위 또는 약 130의 둘러싸는 원 반경을 가지며, 이는 임계 값보다 큽니다. 멤버 지오메트리 사이의 개별 거리가 임계 값보다 작기 때문에 하나의 더 큰 클러스터로 묶습니다.


2
위대한, 이러한 수정은 설치에 도움이 될 것입니다 :-) 그러나 (확실히 이해한다면) 실제로 확장을 사용할 수 없다고 생각합니다. 정확한 데이터 사전에 괜찮은 하드 코딩 된 마법의 클러스터 수가 필요하기 때문입니다. 사전에 미세 조정할 수는 있지만 임의의 (다양한 필터로 인해) 데이터 세트를 클러스터링하는 데 적합하지 않습니다. 예를 들어 마지막 이미지에서 10 포인트 클러스터의 큰 간격입니다. 그러나 이것은 다른 사람들에게도 도움이 될 것입니다 (afaik).이 확장에 대한 기존 SQL 예제 (확장자 홈페이지의 한 라이너 제외)입니다.
wildpeaks

(아아 내가 댓글을 재조정하기 위해 이전 주석을 삭제 한 것과 동시에 응답했습니다.)
wildpeaks

7
kmeans 클러스터링의 경우 미리 클러스터 수를 지정해야합니다. 그래도 클러스터 수가 필요하지 않은 대체 알고리즘이 있는지 궁금합니다.
djq

1
버전 1.1.0 지금 사용할 수 있습니다 : api.pgxn.org/dist/kmeans/1.1.0/kmeans-1.1.0.zip
djq

1
@maxd 번호 A = πr²이면 r = √ (A / π)입니다.
Mike 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-클러스터 거리.

이 기능을 사용한 결과 :

여기에 이미지 설명을 입력하십시오


큰! 함수를 사용하는 방법에 대한 예를 추가 할 수 있습니까? 감사!
underdark

1
소스 코드를 약간 수정하고 함수 사용 예제를 추가했습니다.
drnextgis

postgres 9.1에서 이것을 사용하고 "FOR innr IN EXECUTE 'SELECT'|| gid || ' AS gid, '|| geom ||' dmn IS NULL 'LOOP "에서 geom FROM으로 다음과 같은 오류가 발생합니다. 어떤 아이디어? 오류 : 세트 값을 수용 할 수없는 컨텍스트에서 호출 된 설정 값 함수
비트 박스

내 테이블의 PG (PostGIS n00b) 에서이 코드를 사용하는 방법을 잘 모르겠습니다. 이 구문을 어디에서 이해할 수 있습니까? 내가 클러스터 할 라트와 Lons의있는 테이블이
MGA

우선 geometry테이블에 열을 작성하고 lonlat을 별도로 저장하지 않고 고유 값 (ID)으로 열을 만들어야합니다.
drnextgis 10

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"하십시오.

    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

이 답변 에서 2)에 주어진 PostGIS 쿼리의 결과를 QGIS에 표시하는 방법은 다음과 같습니다.

QGIS는 동일한 지오메트리 열에서 지오메트리 수집이나 다른 데이터 유형을 처리하지 않으므로 클러스터와 클러스터 포인트를위한 두 개의 레이어를 만들었습니다.

먼저 클러스터의 경우 다각형 만 필요하고 다른 결과는 외로운 점입니다.

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

당신은보다 쉽게 Kmeans 솔루션을 사용할 수 있습니다 ST_ClusterKMeans의 2.3 예에서 PostGIS와의 가능한 방법 :

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;

피처의 경계 상자는 위의 예에서 클러스터 형상으로 사용됩니다. 첫 번째 이미지는 원래 형상을 보여주고 두 번째 이미지는 위의 선택 결과입니다.

원래 형상 기능 클러스터


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.