Просторова кластеризація за допомогою PostGIS?


97

Я шукаю алгоритм просторової кластеризації для його використання в базі даних, що підтримує PostGIS, для особливостей точок. Я збираюся написати функцію plpgsql, яка займає відстань між точками в межах одного кластера, що і вхід. На виході функція повертає масив кластерів. Найбільш очевидним рішенням є побудова буферних зон із заданою відстані навколо функції та пошук функцій у цьому буфері. Якщо такі функції існують, то продовжуйте створювати навколо них буфер тощо. Якщо таких функцій не існує, це означає, що побудова кластера завершена. Можливо, є якісь розумні рішення?


4
Існує величезна різноманітність методів кластеризації через різний характер даних та різні цілі кластеризації. Для огляду того, що там, і для легкого читання того, що інші роблять для кластеризації матриць відстані, знайдіть на сайті CV @ SE . Насправді "вибір методу кластеризації" - це майже точний ваш дублікат і має хороші відповіді.
whuber

8
+1 до питання, тому що знайти фактичний приклад PostGIS SQL замість посилань на алгоритми - це місія неможлива ні для чого іншого, крім базового кластеризації сітки, особливо для більш екзотичних кластерів, таких як MCL
wildpeaks

Відповіді:


112

Існують щонайменше два хороших методу кластеризації для PostGIS: k -значення (через kmeans-postgresqlрозширення) або кластеризація геометрії в межах порогової відстані (PostGIS 2.2)


1) k -значить сkmeans-postgresql

Встановлення: Вам потрібно мати PostgreSQL 8.4 або новішої версії в хост-системі POSIX (я не знаю, з чого почати для MS Windows). Якщо ви встановили це з пакетів, переконайтеся, що у вас є також пакети розробки (наприклад, postgresql-develдля CentOS). Завантажте та витягніть:

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

(Примітка. Я також спробував це з 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;

5я забезпечив у другому аргументі kmeansфункції вікна є До цілому числу , щоб зробити п'ять кластерів. Ви можете змінити це на будь-яке ціле число, яке ви хочете.

Нижче представлено 31 псевдо випадкову точку, яку я намалював, і п’ять центроїдів з міткою, що показує кількість у кожному кластері. Це було створено за допомогою вищезазначеного запиту 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 і повертає масив GeometryCollections, де всі компоненти знаходяться на відстані один від одного.

Ось приклад використання, де відстань 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;

КластерWithin100

Найбільший середній скупчення має радіус оточуючого кола 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 / π).
Майк 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 ||' ЯК geom ВІД tmp WHERE dmn IS NULL 'LOOP "призводить до наступної помилки. Будь-які ідеї? ПОМИЛКА: функція встановленої вартості, викликана в контексті, який не може прийняти набір
бітбокс

Я не впевнений, як використовувати цей код у PG (PostGIS n00b) у своїй таблиці. з чого я міг би почати розуміти цей синтаксис? У мене є таблиця з латами і Лон , що я хочу , щоб кластер
MGA

Перш за все, вам потрібно створити geometryстовпчик у вашій таблиці, а не зберігати лонлат окремо і не робити колонку з унікальними значеннями (ідентифікаторами).
drnextgis

10

Поки що найбільш перспективним я знайшов це розширення для кластеризації K-засобів як віконної функції: 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.
  • Тоді ви можете "створити" (посилання) UDF в PostgreSQL за допомогою команди SQL.

Кроки:

  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

Ось спосіб відобразити в QGIS результат запиту PostGIS, наведений у 2) у цій програмі

Оскільки 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, який доступний у postgis з 2.3 Приклад:

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

Рішення щодо кластеризації знизу вгору Отримати єдиний кластер із хмари точок з максимальним діаметром у постгігах, що не передбачає ніяких динамічних запитів.

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

Використання:

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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.