การสร้างแผนภาพ Voronoi ใน PostGIS


12

ฉันพยายามที่จะสร้างแผนภาพ Voronoi จากตารางของจุดใช้รหัสดัดแปลงมาจากที่นี่ นี่คือแบบสอบถาม SQL หลังจากแก้ไขของฉัน:

DROP TABLE IF EXISTS example.voronoi;
WITH 
    -- Sample set of points to work with
    Sample AS (SELECT ST_SetSRID(ST_Union(geom), 0) geom FROM example."MeshPoints2d"),
    -- Build edges and circumscribe points to generate a centroid
    Edges AS (
    SELECT id,
        UNNEST(ARRAY['e1','e2','e3']) EdgeName,
        UNNEST(ARRAY[
            ST_MakeLine(p1,p2) ,
            ST_MakeLine(p2,p3) ,
            ST_MakeLine(p3,p1)]) Edge,
        ST_Centroid(ST_ConvexHull(ST_Union(-- Done this way due to issues I had with LineToCurve
            ST_CurveToLine(REPLACE(ST_AsText(ST_LineMerge(ST_Union(ST_MakeLine(p1,p2),ST_MakeLine(p2,p3)))),'LINE','CIRCULAR'),15),
            ST_CurveToLine(REPLACE(ST_AsText(ST_LineMerge(ST_Union(ST_MakeLine(p2,p3),ST_MakeLine(p3,p1)))),'LINE','CIRCULAR'),15)
        ))) ct      
    FROM    (
        -- Decompose to points
        SELECT id,
            ST_PointN(g,1) p1,
            ST_PointN(g,2) p2,
            ST_PointN(g,3) p3
        FROM    (
            SELECT (gd).Path id, ST_ExteriorRing((gd).geom) g -- ID andmake triangle a linestring
            FROM (SELECT (ST_Dump(ST_DelaunayTriangles(geom))) gd FROM Sample) a -- Get Delaunay Triangles
            )b
        ) c
    )
SELECT ST_SetSRID((ST_Dump(ST_Polygonize(ST_Node(ST_LineMerge(ST_Union(v, ST_ExteriorRing(ST_ConvexHull(v)))))))).geom, 2180)
INTO example.voronoi
FROM (
    SELECT  -- Create voronoi edges and reduce to a multilinestring
        ST_LineMerge(ST_Union(ST_MakeLine(
        x.ct,
        CASE 
        WHEN y.id IS NULL THEN
            CASE WHEN ST_Within(
                x.ct,
                (SELECT ST_ConvexHull(geom) FROM sample)) THEN -- Don't draw lines back towards the original set
                -- Project line out twice the distance from convex hull
                ST_MakePoint(ST_X(x.ct) + ((ST_X(ST_Centroid(x.edge)) - ST_X(x.ct)) * 2),ST_Y(x.ct) + ((ST_Y(ST_Centroid(x.edge)) - ST_Y(x.ct)) * 2))
            END
        ELSE 
            y.ct
        END
        ))) v
    FROM    Edges x 
        LEFT OUTER JOIN -- Self Join based on edges
        Edges y ON x.id <> y.id AND ST_Equals(x.edge,y.edge)
    ) z

ด้านล่าง - ผลการค้นหาของฉัน ป้อนคำอธิบายรูปภาพที่นี่

อย่างที่คุณเห็นฉันได้รับ "เกือบ" แผนภาพ voronoi ที่ถูกต้องยกเว้นจุดเน้นที่ไม่มีรูปหลายเหลี่ยมที่ไม่ซ้ำกัน ด้านล่างคือสิ่งที่อัลกอริทึมของ QGIS ผลิตขึ้นมาและสิ่งที่ฉันต้องการได้จากแบบสอบถาม ข้อเสนอแนะใด ๆ ที่มีปัญหากับรหัสของฉัน?

ป้อนคำอธิบายรูปภาพที่นี่


บางทีคุณอาจเปรียบเทียบผลลัพธ์ของฟังก์ชัน SpatiaLite " VoronojDiagram " gaia-gis.it/gaia-sins/spatialite-sql-latest.htmlและดูที่ซอร์สโค้ดในgaia-gis.it/fossil/libspatialite/ ดัชนี
user30184

เป็นคำถามที่ดีฉันได้ดูคำถามเดียวกันกับที่คุณอ้างถึงเพื่อเร่งให้มันเร็วขึ้น แต่หมดเวลา ฉันไม่ได้ตระหนักถึงปัญหานี้กับจุดนอก
John Powell

5
สำหรับสิ่งที่คุ้มค่าเรามี ST_Voronoi ที่มาใน PostGIS 2.3, Dan Baston จะตรวจสอบรหัสในเร็ว ๆ นี้ - trac.osgeo.org/postgis/ticket/2259 ดูดีมากที่ต้องดึงเข้าไป การทดสอบ folks
LR1234567

คุณสามารถโพสต์ชุดคะแนนที่คุณใช้หรือไม่ ฉันจะทำแบบทดสอบกับตัวเอง
MickyT

@MickyT นี่คือลิงค์ ไปยังข้อมูลของฉัน Data SRID คือ 2180
DamnBack

คำตอบ:


6

แม้ว่าวิธีนี้จะช่วยแก้ไขปัญหาในทันทีสำหรับการค้นหาข้อมูลที่เป็นปัญหา แต่ฉันไม่พอใจกับวิธีการแก้ปัญหาสำหรับการใช้งานทั่วไปและฉันจะกลับมาทบทวนคำตอบนี้และคำตอบก่อนหน้าเมื่อทำได้

ปัญหาคือว่าแบบสอบถามเดิมใช้เปลือกนูนบนขอบ voronoi เพื่อกำหนดขอบภายนอกสำหรับรูปหลายเหลี่ยม voronoi นี่หมายความว่าขอบบางส่วนของโวโรโนอิไม่ถึงด้านนอกเมื่อควรจะเป็น ฉันดูการใช้ฟังก์ชั่นตัวเรือเว้า แต่มันไม่ได้ผลอย่างที่ฉันต้องการ
เพื่อเป็นการแก้ไขอย่างรวดเร็วฉันได้เปลี่ยนเปลือกนูนเพื่อสร้างรอบชุดขอบ voronoi ที่ปิดบวกกับบัฟเฟอร์รอบขอบเดิม ขอบ vononoi ที่ไม่ปิดจะถูกขยายออกเป็นระยะทางไกลเพื่อให้แน่ใจว่าพวกมันตัดกันด้านนอกและใช้ในการสร้างรูปหลายเหลี่ยม คุณอาจต้องการเล่นกับพารามิเตอร์บัฟเฟอร์

WITH 
    -- Sample set of points to work with
    Sample AS (SELECT ST_SetSRID(ST_Union(geom), 0) geom FROM MeshPoints2d),
    -- Build edges and circumscribe points to generate a centroid
    Edges AS (
    SELECT id,
        UNNEST(ARRAY['e1','e2','e3']) EdgeName,
        UNNEST(ARRAY[
            ST_MakeLine(p1,p2) ,
            ST_MakeLine(p2,p3) ,
            ST_MakeLine(p3,p1)]) Edge,
        ST_Centroid(ST_ConvexHull(ST_Union(-- Done this way due to issues I had with LineToCurve
            ST_CurveToLine(REPLACE(ST_AsText(ST_LineMerge(ST_Union(ST_MakeLine(p1,p2),ST_MakeLine(p2,p3)))),'LINE','CIRCULAR'),15),
            ST_CurveToLine(REPLACE(ST_AsText(ST_LineMerge(ST_Union(ST_MakeLine(p2,p3),ST_MakeLine(p3,p1)))),'LINE','CIRCULAR'),15)
        ))) ct      
    FROM    (
        -- Decompose to points
        SELECT id,
            ST_PointN(g,1) p1,
            ST_PointN(g,2) p2,
            ST_PointN(g,3) p3
        FROM    (
            SELECT (gd).Path id, ST_ExteriorRing((gd).geom) g -- ID andmake triangle a linestring
            FROM (SELECT (ST_Dump(ST_DelaunayTriangles(geom))) gd FROM Sample) a -- Get Delaunay Triangles
            )b
        ) c
    )
SELECT ST_SetSRID((ST_Dump(ST_Polygonize(ST_Node(ST_LineMerge(ST_Union(v, (SELECT ST_ExteriorRing(ST_ConvexHull(ST_Union(ST_Union(ST_Buffer(edge,20),ct)))) FROM Edges))))))).geom, 2180) geom
INTO voronoi
FROM (
    SELECT  -- Create voronoi edges and reduce to a multilinestring
        ST_LineMerge(ST_Union(ST_MakeLine(
        x.ct,
        CASE 
        WHEN y.id IS NULL THEN
            CASE WHEN ST_Within(
                x.ct,
                (SELECT ST_ConvexHull(geom) FROM sample)) THEN -- Don't draw lines back towards the original set
                -- Project line out twice the distance from convex hull
                ST_MakePoint(ST_X(x.ct) + ((ST_X(ST_Centroid(x.edge)) - ST_X(x.ct)) * 200),ST_Y(x.ct) + ((ST_Y(ST_Centroid(x.edge)) - ST_Y(x.ct)) * 200))
            END
        ELSE 
            y.ct
        END
        ))) v
    FROM    Edges x 
        LEFT OUTER JOIN -- Self Join based on edges
        Edges y ON x.id <> y.id AND ST_Equals(x.edge,y.edge)
    ) z;

ขอบคุณสำหรับคำอธิบายและแก้ไขปัญหาอย่างรวดเร็ว! ใช้งานได้กับข้อมูลของฉัน (ช้าลงเล็กน้อยเนื่องจากST_Union(ST_Buffer(geom))) แต่ฉันจะทำการทดสอบต่อกับชุดคะแนนอื่น ๆ ในขณะเดียวกันฉันจะรออย่างที่คุณพูด - แก้ปัญหาทั่วไปมากกว่า :)
DamnBack

คุณมีภาพที่คุณสามารถโพสต์ในผลลัพธ์สุดท้ายของคุณ?
Jeryl Cook

10

ทำตามคำแนะนำโดย @ LR1234567 เพื่อลองใช้ฟังก์ชั่นST_Voronoiใหม่ที่พัฒนาโดย @dbaston ซึ่งเป็นคำตอบที่น่าอัศจรรย์ดั้งเดิมของ @MickyT (ตามที่ระบุไว้ในคำถามของ OP) และการใช้ข้อมูลต้นฉบับสามารถทำให้ง่ายขึ้นไป:

WITH voronoi (vor) AS 
     (SELECT ST_Dump(ST_Voronoi(ST_Collect(geom))) FROM meshpoints)
SELECT (vor).path, (vor).geom FROM voronoi;

ซึ่งผลลัพธ์ในผลลัพธ์นี้เหมือนกับคำถามของ OP

ป้อนคำอธิบายรูปภาพที่นี่

อย่างไรก็ตามสิ่งนี้ได้รับผลกระทบจากปัญหาเดียวกันตอนนี้ได้รับการแก้ไขในคำตอบของ MickyT ซึ่งจุดบนลำตัวเว้าไม่ได้รับรูปหลายเหลี่ยมที่ล้อมรอบ ฉันแก้ไขปัญหานี้ด้วยแบบสอบถามด้วยขั้นตอนต่อไปนี้

  1. คำนวณลำเรือเว้าของจุดอินพุท - จุดบนลำเรือเว้าคือจุดที่มีรูปหลายเหลี่ยมมากมายในแผนภาพ Voronoi เอาท์พุท
  2. ค้นหาจุดเริ่มต้นของตัวเรือเว้า (จุดสีเหลืองในแผนภาพที่ 2 ด้านล่าง)
  3. บัฟเฟอร์ฮัลล์แบบเว้า (ระยะห่างของบัฟเฟอร์เป็นไปตามอำเภอใจและอาจพบได้ดีที่สุดจากข้อมูลอินพุต?)
  4. ค้นหาจุดที่ใกล้ที่สุดบนบัฟเฟอร์ของลำตัวเว้าใกล้กับจุดในขั้นตอนที่ 2 ซึ่งแสดงเป็นสีเขียวในแผนภาพด้านล่าง
  5. เพิ่มจุดเหล่านี้ไปยังชุดข้อมูลดั้งเดิม
  6. คำนวณไดอะแกรม Voronoi ของชุดข้อมูลที่รวมกันนี้ ดังที่เห็นในแผนภาพที่ 3 ตอนนี้จุดบนตัวถังมีรูปหลายเหลี่ยมล้อมรอบ

แผนภาพที่ 2 แสดงจุดบนตัวเรือเว้า (สีเหลือง) และจุดที่ใกล้เคียงที่สุดเพื่อบัฟเฟอร์บนตัวถัง (สีเขียว) แผนภาพที่ 2

เห็นได้ชัดว่าแบบสอบถามนั้นสามารถทำให้เรียบง่าย / ถูกบีบอัดได้ แต่ฉันปล่อยให้มันเป็นแบบฟอร์มนี้เป็นชุดของ CTE เนื่องจากมันง่ายกว่าที่จะทำตามขั้นตอนตามลำดับด้วยวิธีนั้น แบบสอบถามนี้ทำงานกับชุดข้อมูลต้นฉบับเป็นมิลลิวินาที (เฉลี่ย 11 มิลลิวินาทีบนเซิร์ฟเวอร์ dev) ในขณะที่คำตอบของ MickyT ที่ใช้ ST_Delauney ทำงานใน 4800ms บนเซิร์ฟเวอร์เดียวกัน DBaston อ้างว่าคำสั่งของการเพิ่มความเร็วขนาดอื่นสามารถได้รับจากการสร้างกับลำตัวปัจจุบันของ GEOS 3.6dev เนื่องจากการปรับปรุงในการปฏิบัติสมการสามเหลี่ยม

WITH 
  conv_hull(geom) AS 
        (SELECT ST_Concavehull(ST_Union(geom), 1) FROM meshpoints), 
  edge_points(points) AS 
        (SELECT mp.geom FROM meshpoints mp, conv_hull ch 
        WHERE ST_Touches(ch.geom, mp.geom)), 
  buffered_points(geom) AS
        (SELECT ST_Buffer(geom, 100) as geom FROM conv_hull),
  closest_points(points) AS
        (SELECT 
              ST_Closestpoint(
                   ST_Exteriorring(bp.geom), ep.points) as points,
             ep.points as epoints 
         FROM buffered_points bp, edge_points ep),
  combined_points(points) AS
        (SELECT points FROM closest_points 
        UNION SELECT geom FROM meshpoints),
  voronoi (vor) AS 
       (SELECT 
            ST_Dump(
                  ST_Voronoi(
                    ST_Collect(points))) as geom 
        FROM combined_points)
 SELECT 
     (vor).path[1] as id, 
     (vor).geom 
 FROM voronoi;

ไดอะแกรม 3 แสดงจุดทั้งหมดที่อยู่ในรูปหลายเหลี่ยม แผนภาพ 3

หมายเหตุ:ปัจจุบัน ST_Voronoi เกี่ยวข้องกับการสร้าง Postgis จากต้นทาง (เวอร์ชัน 2.3 หรือลำตัว) และเชื่อมโยงกับ GEOS 3.5 หรือสูงกว่า

แก้ไข:ฉันเพิ่งดู Postgis 2.3 เนื่องจากติดตั้งบน Amazon Web Services และดูเหมือนว่าชื่อฟังก์ชันคือ ST_VoronoiPolygons

ไม่ต้องสงสัยเลยว่าแบบสอบถาม / อัลกอริทึมนี้สามารถปรับปรุงได้ ยินดีต้อนรับข้อเสนอแนะ


@dbaston สงสัยว่าคุณมีความคิดเห็นใด ๆ เกี่ยวกับวิธีการนี้หรือไม่?
John Powell

1
ดีทุกจุดไม่ได้รับรูปหลายเหลี่ยมล้อมรอบก็แค่ว่ามันเป็นขนาดใหญ่เป็นสัดส่วน ถ้าและวิธีการเหล่านี้จะทำให้เล็กลงเป็นเรื่องส่วนตัวและโดยไม่รู้ว่าสิ่งที่ต้องการสำหรับรูปหลายเหลี่ยมด้านนอกก็ยากที่จะรู้ว่าวิธีที่ "ดีที่สุด" คืออะไร คุณดูเหมือนจะเป็นวิธีที่ดีสำหรับฉัน ฉันใช้วิธีการที่ซับซ้อนน้อยกว่าซึ่งคล้ายกับจิตวิญญาณของคุณวางจุดพิเศษตามขอบเขตตัวเรือนูนที่ระยะห่างคงที่ซึ่งพิจารณาจากความหนาแน่นของจุดเฉลี่ย
dbaston

@dbaston ขอบคุณแค่ทำให้แน่ใจว่าฉันจะไม่พลาดสิ่งที่เห็นได้ชัด อัลกอริทึมสำหรับลดขนาดรูปหลายเหลี่ยมด้านนอกให้เล็กลงตามขนาดของชิ้นส่วนด้านใน (ในพื้นที่รหัสไปรษณีย์ของฉัน) เป็นสิ่งที่ฉันต้องคิดอีก
John Powell

@John Barçaขอบคุณสำหรับวิธีแก้ปัญหาที่ยอดเยี่ยมอีกอย่างหนึ่ง ความเร็วในการคำนวณเป็นมากกว่าที่พอใจกับวิธีการนี้ น่าเสียดายที่ฉันต้องการใช้อัลกอริทึมนี้ภายในปลั๊กอิน QGIS ของฉันและต้องทำงานกับ PostGIS 2.1+ นอกกรอบ แต่แน่นอนฉันจะใช้วิธีแก้ไขปัญหานี้หลังจากการเปิดตัว PostGIS 2.3 อย่างเป็นทางการ อย่างไรก็ตามขอขอบคุณสำหรับคำตอบที่ครอบคลุมดังกล่าว :)
DamnBack

@DamnBack ด้วยความยินดีอย่างยิ่ง. ฉันต้องการสิ่งนี้สำหรับการทำงานและคำถามของคุณช่วยฉันได้มากเพราะฉันไม่รู้ว่า ST_Voronoi กำลังจะออกมาและการแก้ปัญหาแบบเก่านั้นช้ากว่ามาก (ดังที่คุณสังเกตเห็น) มันสนุกมากที่ได้รู้ว่ามันเกินไป :-)
John Powell

3

หากคุณมีสิทธิ์เข้าถึง PostGIS 2.3 ให้ลองใช้งานฟังก์ชั่น ST_Voronoi ใหม่ล่าสุด:

http://postgis.net/docs/manual-dev/ST_Voronoi.html

มีการสร้าง precompiled สำหรับ windows - http://postgis.net/windows_downloads/


ขอบคุณสำหรับข้อมูลที่มีฟังก์ชั่นในตัว ST_Voronoi ที่อัปเดตแล้ว - ฉันจะลองดู น่าเสียดายที่ฉันต้องการโซลูชันที่ทำงานกับรุ่น PostGIS 2.1+ ดังนั้นเคียวรี @MickyT นั้นใกล้เคียงกับความต้องการของฉันมากที่สุดในขณะนี้
DamnBack

@ LR1234567 สิ่งนี้ต้องการ GEOS รุ่นใด ๆ หรือไม่ พรุ่งนี้ฉันมีเวลาทดสอบ 2.3 และ ST_Voronoi
John Powell

ต้องใช้ GEOS 3.5
LR1234567
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.