Come usare ST_DelaunayTriangles per costruire un diagramma Voronoi?


13

(modifica 2019) ST_VoronoiPolygons disponibile da PostGIS v2.3 !


Con PostGIS 2.1+ possiamo usare ST_DelaunayTriangles () per generare una triangolazione Delaunay , ovvero un doppio grafico del diagramma Voronoi e, in teoria, hanno una conversione esatta e reversibile.

Esiste uno script SQL standard sicuro con un algoritmo ottimizzato per questa conversione da Delaunay a Voronoi PostGIS2 ?


Altri riferimenti: 1 , 2


Gist.github.com/djq/4714788 è il tipo di cosa che cerchi?
MickyT,

Penso che voglia un'implementazione puramente SQL usando ST_DelaunayTriangles ()
raphael,

Vedi questa risposta per l'installazione ST_DelaunayTrianglesin Linux Debian Stable .
Peter Krauss,

! ST_VoronoiPolygons disponibile da PostGIS 2.3
Peter Krauss

Risposte:


23

La seguente query sembra fare una serie ragionevole di poligoni voronoi a partire dai triangoli di Delaunay.

Non sono un grande utente di Postgres, quindi probabilmente può essere migliorato abbastanza.

WITH 
    -- Sample set of points to work with
    Sample AS (SELECT ST_GeomFromText('MULTIPOINT (12 5, 5 7, 2 5, 19 6, 19 13, 15 18, 10 20, 4 18, 0 13, 0 6, 4 1, 10 0, 15 1, 19 6)') geom),
    -- 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_Polygonize(ST_Node(ST_LineMerge(ST_Union(v, ST_ExteriorRing(ST_ConvexHull(v))))))
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;

Questo produce il seguente set di poligoni per i punti campione inclusi nella query inserisci qui la descrizione dell'immagine

Spiegazione della query

Passo 1

Crea i triangoli Delaunay dalle geometrie di input

SELECT (gd).Path id, ST_ExteriorRing((gd).Geom) g -- ID and make triangle a linestring
FROM (SELECT (ST_Dump(ST_DelaunayTriangles(geom))) gd FROM Sample) a -- Get Delaunay Triangles

Passo 2

Decomporre i nodi del triangolo e creare bordi. Penso che dovrebbe esserci un modo migliore per ottenere i bordi, ma non ne ho trovato uno.

SELECT ...
        ST_MakeLine(p1,p2) ,
        ST_MakeLine(p2,p3) ,
        ST_MakeLine(p3,p1)
        ...
FROM    (
    -- Decompose to points
    SELECT id,
        ST_PointN(g,1) p1,
        ST_PointN(g,2) p2,
        ST_PointN(g,3) p3
    FROM    (
        ... Step 1...
        )b
    ) c

inserisci qui la descrizione dell'immagine

Passaggio 3

Costruisci i cerchi circoscritti per ogni triangolo e trova il centroide

SELECT ... Step 2 ...
    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    (
        ... Step 1...
        )b
    ) c

inserisci qui la descrizione dell'immagine

Il EdgesCTE emette ciascun fronte e l'id (percorso) del triangolo a cui appartiene.

Passaggio 4

'Unisci esterno' la tabella 'Bordi' a se stessa dove ci sono bordi uguali per diversi triangoli (bordi interni).

SELECT  
    ...
    ST_MakeLine(
    x.ct, -- Circumscribed Circle centroid
    CASE 
    WHEN y.id IS NULL THEN
        CASE WHEN ST_Within( -- Don't draw lines back towards the original set
            x.ct,
            (SELECT ST_ConvexHull(geom) FROM sample)) THEN
            -- 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),
                T_Y(x.ct) + ((ST_Y(ST_Centroid(x.edge)) - ST_Y(x.ct)) * 2)
            )
        END
    ELSE 
        y.ct -- Centroid of triangle with common edge
    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)

Dove c'è un bordo comune traccia una linea tra i rispettivi centroidi

inserisci qui la descrizione dell'immagine

Se il bordo non è unito (esterno) tracciare una linea dal centroide attraverso il centro del bordo. Fallo solo se il centroide del cerchio si trova all'interno della serie di triangoli.

inserisci qui la descrizione dell'immagine

Passaggio 5

Prendi lo scafo convesso per le linee disegnate come una linea. Unisci e unisci tutte le linee. Nodo l'insieme di linee in modo da disporre di un insieme topologico che può essere poligonizzato.

SELECT ST_Polygonize(ST_Node(ST_LineMerge(ST_Union(v, ST_ExteriorRing(ST_ConvexHull(v))))))

inserisci qui la descrizione dell'immagine


Buon indizio, forse una soluzione (!). Ho bisogno di prova, ma non posso ora ... Analizzando: si usa ST_ConvexHulle ST_Centroidinvece "mediane", come nell'algoritmo diretta suggerito dal mio RIF1 / Kenneth SLOA ... Perché non la soluzione diretta?
Peter Krauss,

Praticamente sto facendo bisettrici perpendicolari per i bordi esterni, ma senza tutta la matematica :) Aggiungerò una spiegazione dei passi che ho fatto alla risposta
MickyT

Buone illustrazioni e spiegazioni, molto didattiche!   Hai pubblicato tutto ciò di cui ho bisogno (!), Ma oggigiorno non ho Postgis2.1 da testare ... Posso controllare qui (come commento) alcune domande a cui qualcuno può rispondere testando?   1) ST_Polygonize "crea un GeometryCollection contenente possibili poligoni", sono tutte celle Voronoi, giusto?   2) riguardo alle prestazioni, pensi che la tua soluzione basata su centroidi abbia un tempo CPU simile a "tutta la matematica del calcolo delle bisettrici perpendicolari"?
Peter Krauss,

@PeterKrauss 1) ST_polygonize crea le celle voronoi dal lavoro di linea. Il trucco è assicurarsi che tutto il lavoro di linea sia diviso sui nodi. 2) Non credo che ci sarebbe molta differenza tra il calcolo della dissezione e l'uso di ST_Centroid sulla linea. Ma avrebbe bisogno di essere testato.
MickyT

Vedi questa risposta per l'installazione ST_DelaunayTrianglesin Linux Debian Stable .
Peter Krauss,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.