PostGIS के साथ स्थानिक क्लस्टरिंग?


97

मैं बिंदु सुविधाओं के लिए PostGIS- सक्षम डेटाबेस के भीतर इसका उपयोग करने के लिए स्थानिक क्लस्टरिंग एल्गोरिथ्म की तलाश कर रहा हूं। मैं plpgsql फ़ंक्शन लिखने जा रहा हूं जो इनपुट के समान क्लस्टर के भीतर बिंदुओं के बीच दूरी लेता है। उत्पादन समारोह में समूहों की सरणी देता है। सबसे स्पष्ट समाधान फीचर के आसपास बफर जोन निर्दिष्ट दूरी का निर्माण करना है और इस बफर में सुविधाओं की खोज करना है। यदि ऐसी सुविधाएँ मौजूद हैं, तो उनके आसपास एक बफर का निर्माण जारी रखें, आदि यदि ऐसी सुविधाएँ मौजूद नहीं हैं, जिसका अर्थ है कि क्लस्टर निर्माण पूरा हो गया है। शायद कुछ चतुर समाधान हैं?


4
डेटा की बदलती प्रकृति और क्लस्टरिंग के विभिन्न उद्देश्यों के कारण क्लस्टरिंग विधियों की एक विशाल विविधता है। वहाँ क्या हो रहा है और दूसरों के लिए क्लस्टर मैट्रिस क्लस्टर करने के बारे में कुछ आसान पढ़ने के लिए अवलोकन के लिए, CV @ एसई साइट खोजें । वास्तव में, "क्लस्टरिंग विधि चुनना" लगभग आपकी एक सटीक डुप्लिकेट है और इसके अच्छे उत्तर हैं।
whuber

8
+1 प्रश्न क्योंकि एल्गोरिदम के लिंक के बजाय एक वास्तविक पोस्टगिस एसक्यूएल उदाहरण खोजना बुनियादी ग्रिड क्लस्टरिंग के अलावा किसी भी चीज के लिए असंभव है, विशेष रूप से एमसीएल
वाइल्डपीक्स

जवाबों:


112

PostGIS के लिए कम से कम दो अच्छे क्लस्टरिंग तरीके हैं: k -means ( kmeans-postgresqlएक्सटेंशन के माध्यम से ) या एक थ्रेसहोल्ड दूरी के भीतर क्लस्टरिंग ज्यामितीय (PostGIS 2.2)


1) k -means के साथkmeans-postgresql

स्थापना: आपको पोस्टग्रेएसक्यूएल 8.4 या पोसिक्स होस्ट सिस्टम पर अधिक होना चाहिए (मुझे नहीं पता कि एमएस विंडोज के लिए कहां से शुरू करना है)। यदि आपने इसे संकुल से स्थापित किया है, तो सुनिश्चित करें कि आपके पास विकास पैकेज (जैसे, 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, जो विकल्पों में से सबसे अच्छा नहीं था)। इन दो आदेशों में से एक को आपके यूनिक्स शेल के लिए काम करना चाहिए:

# 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;

5का दूसरा तर्क में प्रदान की मैं kmeansखिड़की समारोह है कश्मीर पूर्णांक पाँच समूहों का निर्माण करने के। आप इसे अपने इच्छित पूर्णांक में बदल सकते हैं।

नीचे 31 छद्म यादृच्छिक बिंदु हैं जिन्हें मैंने आकर्षित किया है और प्रत्येक क्लस्टर में गिनती दिखाते हुए लेबल के साथ पांच सेंट्रोइड हैं। यह उपरोक्त 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 के साथ शामिल है, और ज्यामिति के एक सरणी को लौटाता है जहां सभी घटक एक दूसरे से कुछ दूरी पर हैं।

यहां एक उदाहरण का उपयोग किया गया है, जहां 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 का एक संलग्न घेरा त्रिज्या है, जो दहलीज से बड़ा है। ऐसा इसलिए है क्योंकि सदस्य ज्यामितीयों के बीच की व्यक्तिगत दूरी थ्रेशोल्ड से कम है, इसलिए यह इसे एक बड़े क्लस्टर के रूप में एक साथ जोड़ता है।


2
महान, ये संशोधन स्थापना के लिए मदद करेंगे :-) लेकिन मुझे डर है कि मैं वास्तव में उस विस्तार का उपयोग नहीं कर सकता क्योंकि (अगर मुझे सही ढंग से समझा गया है), तो इसे क्लस्टर के हार्डकोड मैजिक नंबर की आवश्यकता है, जो स्थिर डेटा के साथ ठीक है आप इसे पहले से ठीक कर सकते हैं लेकिन मुझे मनमाने ढंग से (विभिन्न फिल्टर के कारण) डेटा सेट के लिए फिट नहीं कर पाएंगे, उदाहरण के लिए अंतिम छवि पर 10-पॉइंट क्लस्टर में बड़ा अंतर। हालाँकि, यह अन्य लोगों को भी मदद करेगा क्योंकि (afaik), यह एकमात्र मौजूदा SQL उदाहरण है (विस्तार के होमपेज पर एक लाइनर को छोड़कर) उस विस्तार के लिए।
वाइल्डपिक्स जूल

(आह, आपने उसी समय उत्तर दिया कि मैंने पिछली टिप्पणी को इसे सुधारने के लिए हटा दिया है, क्षमा करें)
Wildpeaks

7
किमी के क्लस्टरिंग के लिए आपको पहले से क्लस्टर की संख्या निर्दिष्ट करने की आवश्यकता है; यदि वैकल्पिक एल्गोरिदम हैं, जहां समूहों की संख्या की आवश्यकता नहीं है, तो मैं उत्सुक हूं।
djq 15

1
संस्करण 1.1.0 अब उपलब्ध है: api.pgxn.org/dist/kmeans/1.1.0/kmeans-1.1.0.zip
djq

1
@ मोमक्स नं। दिए गए ए = ²rπ, फिर आर = A (ए /।)।
माइक टी

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

बस का उपयोग करने की कोशिश की पोस्ट 9.1 और रेखा "के लिए innr में EXECUTE 'का चयन करें' || gid '|| एएस गिद, '|| जियोम || AS जम्प से tmp जहां dmn है, वहां '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 के उत्तर को लागू करते हुए ...

एमएस विंडोज के लिए:

आवश्यकताएँ:

  • जैसे किसी भी विजुअल C ++ एक्सप्रेस संस्करण इस
  • Kmeans-PostgreSQL मॉड्यूल।

आप क्या करोगे:

  • किमी डीएलएल को निर्यात करने के लिए स्रोत कोड को मोड़ दें।
  • फ़ंक्शन के cl.exeसाथ DLL जनरेट करने के लिए संकलक के साथ स्रोत कोड संकलित करें kmeans
  • उत्पन्न DLL को PostgreSQL \ lib फ़ोल्डर में डालें।
  • फिर आप SQL कमांड के माध्यम से PostgreSQL में UDF "लिंक" (लिंक) बना सकते हैं।

कदम:

  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. दृश्य C ++ कमांड लाइन खोलें।

  4. कमांड लाइन में:

    1. निकाले जाओ kmeans-postgresql
    2. उदाहरण के लिए, अपना POSTGRESPATH सेट करें: SET POSTGRESPATH=C:\Program Files\PostgreSQL\9.5
    3. Daud

      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 में दिए गए प्रश्न) का परिणाम एक तरीका है इस anwser

क्यूजीआईएस के रूप में एक ही ज्यामिति कॉलम में न तो ज्यामिति और न ही अलग-अलग डेटाटिप्स संभालती हैं, मैंने दो परतें बनाई हैं, एक क्लस्टर के लिए और एक क्लस्टर बिंदुओं के लिए।

क्लस्टर के लिए पहले, आपको केवल बहुभुज की आवश्यकता होती है, अन्य परिणाम अकेले बिंदु होते हैं:

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 उदाहरण से पोस्टगिस में उपलब्ध 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;

उपरोक्त उदाहरण में क्लस्टर ज्यामिति के रूप में सुविधाओं की बाउंडिंग बॉक्स का उपयोग किया जाता है। पहली छवि मूल ज्यामिति दिखाती है और दूसरी ऊपर के चयन का परिणाम है।

मूल ज्यामिति फ़ीचर क्लस्टर


1

से नीचे से ऊपर क्लस्टरिंग समाधान 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

उपयोग:

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 Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.