Costruzione del diagramma Voronoi in PostGIS


12

Sto provando a costruire il diagramma voronoi dalla griglia di punti usando il codice modificato da qui . Questa è la query SQL dopo le mie modifiche:

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

Sotto - risultato della mia domanda. inserisci qui la descrizione dell'immagine

Come puoi vedere ottengo un diagramma voronoi "quasi" corretto, tranne i punti evidenziati che non hanno un poligono unico. Di seguito è riportato ciò che produce l'algoritmo QGIS e cosa vorrei ottenere dalla query. Qualche suggerimento dov'è il problema con il mio codice?

inserisci qui la descrizione dell'immagine


Forse potresti anche confrontare il risultato della funzione SpatiaLite " VoronojDiagram " gaia-gis.it/gaia-sins/spatialite-sql-latest.html e dare un'occhiata al codice sorgente in gaia-gis.it/fossil/libspatialite/ indice .
user30184

Bella domanda, ho esaminato la stessa domanda a cui fai riferimento, al fine di accelerarla, ma continuo a rimanere senza tempo. Non ero a conoscenza di questo problema con i punti esterni.
John Powell,

5
Per quello che vale ST_Voronoi in arrivo su PostGIS 2.3, Dan Baston controllerà presto il codice per farlo - trac.osgeo.org/postgis/ticket/2259 sembra praticamente fatto, deve solo essere inserito . Sarebbe bello averlo test di gente
LR1234567

Puoi pubblicare l'insieme di punti che stai utilizzando? Mi dispiacerebbe fare un po 'di prove su questo
MickyT

@MickyT Ecco un link ai miei dati. Data SRID è un 2180.
DamnBack

Risposte:


6

Mentre questo risolve il problema immediato con la query per i dati in questione, non sono contento di come soluzione per l'uso generale e rivedrò questa e la risposta precedente quando posso.

Il problema era che la query originale utilizzava uno scafo convesso sui bordi del voronoi per determinare il bordo esterno del poligono voronoi. Ciò significava che alcuni dei bordi dei voronoi non raggiungevano l'esterno quando avrebbero dovuto essere. Ho esaminato l'utilizzo della funzionalità concava dello scafo, ma non ha funzionato come volevo.
Come soluzione rapida ho cambiato lo scafo convesso da costruire attorno alla serie chiusa di bordi voronoi più un cuscinetto attorno ai bordi originali. I bordi del voronoi che non si chiudono sono estesi per una grande distanza per cercare di assicurarsi che si intersecano con l'esterno e che vengano utilizzati per costruire poligoni. Potresti voler giocare con i parametri del buffer.

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;

Grazie per la spiegazione e la soluzione rapida per il problema! Funziona con i miei dati (un po 'più lento a causa di ST_Union(ST_Buffer(geom))), ma continuerò a testare con altri set di punti. Nel frattempo aspetterò, come hai detto, una soluzione più generale. :)
DamnBack,

hai un'immagine che puoi pubblicare sul tuo output finale?
Jeryl Cook,

10

Seguendo un suggerimento di @ LR1234567 per provare la nuova funzionalità ST_Voronoi che è stata sviluppata da @dbaston, la sorprendente risposta originale di @MickyT (come indicato nella domanda dell'OP) e l'utilizzo dei dati originali ora può essere semplificata per:

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

che risulta in questo risultato, identico alla domanda del PO.

inserisci qui la descrizione dell'immagine

Tuttavia, questo soffre dello stesso problema, ora risolto nella risposta di MickyT, che i punti sullo scafo concavo non ottengono un poligono di chiusura (come ci si aspetterebbe dall'algoritmo). Ho risolto questo problema con una query con le seguenti serie di passaggi.

  1. Calcola lo scafo concavo dei punti di input - i punti sullo scafo concavo sono quelli che hanno poligoni illimitati nel diagramma Voronoi di output.
  2. Trova i punti originali sullo scafo concavo (punti gialli nel diagramma 2 in basso).
  3. Buffer lo scafo concavo (la distanza del buffer è arbitraria e potrebbe forse essere trovata in modo più ottimale dai dati di input?).
  4. Trova i punti più vicini sul buffer dello scafo concavo più vicini ai punti nel passaggio 2. Questi sono mostrati in verde nel diagramma seguente.
  5. Aggiungi questi punti al set di dati originale
  6. Calcola il diagramma Voronoi di questo set di dati combinato. Come si può vedere nel 3 ° diagramma, i punti sullo scafo ora hanno poligoni chiusi.

Diagramma 2 che mostra i punti sullo scafo concavo (giallo) e i punti più vicini al buffer sullo scafo (verde). Diagramma 2.

La query potrebbe ovviamente essere semplificata / compressa, ma ho lasciato questo modulo come una serie di CTE, in quanto è più facile seguire i passaggi in sequenza in quel modo. Questa query viene eseguita sul set di dati originale in millisecondi (in media 11ms su un server dev) mentre la risposta di MickyT utilizzando ST_Delauney viene eseguita su 4800ms sullo stesso server. DBaston afferma che un altro ordine di miglioramento della velocità della grandezza può essere ottenuto dalla costruzione contro l'attuale tronco di GEOS, 3.6dev, a causa di miglioramenti nelle routine di triangolazione.

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;

Diagramma 3 che mostra tutti i punti ora racchiusi in un poligono diagramma 3

Nota: Attualmente ST_Voronoi prevede la creazione di Postgis dalla fonte (versione 2.3 o trunk) e il collegamento con GEOS 3.5 o versioni successive.

Modifica: ho appena visto Postgis 2.3 mentre è installato su Amazon Web Services e sembra che il nome della funzione sia ora ST_VoronoiPolygons.

Senza dubbio questa query / algoritmo potrebbe essere migliorata. Suggerimenti benvenuti.


@dbaston. Ti chiedi se hai commenti su questo approccio?
John Powell,

1
bene, tutti i punti fanno ottenere un poligono che racchiude, è solo che è sproporzionatamente grande. Se e come ridurli siano piuttosto soggettivi, e senza sapere esattamente cosa si desidera per i poligoni esterni è difficile sapere quale sia il modo "migliore". Il tuo mi sembra un ottimo metodo. Ho usato un metodo meno sofisticato che è simile nello spirito al tuo, facendo cadere punti extra lungo un confine dello scafo convesso tamponato ad una spaziatura fissa determinata dalla densità del punto medio.
dbaston,

@dbaston. Grazie, assicurandomi solo che non mi mancasse qualcosa di ovvio. Un algoritmo per ridurre i poligoni esterni a qualcosa di più in linea con la dimensione di quelli interni (nel mio caso le aree del codice postale) è qualcosa su cui dovrò riflettere un po 'di più.
John Powell,

@ John Barça Grazie per un'altra ottima soluzione. La velocità dei calcoli è più che soddisfacente con questo approccio. Sfortunatamente vorrei usare questo algoritmo all'interno del mio plugin QGIS, e deve funzionare con PostGIS 2.1+ immediatamente. Ma sicuramente userò questa soluzione dopo il rilascio ufficiale di PostGIS 2.3. Comunque grazie per risposte così complete. :)
DamnBack,

@DamnBack. Di niente. Ne avevo bisogno per lavoro e la tua domanda mi ha davvero aiutato molto, dato che non avevo idea dell'uscita di ST_Voronoi e le soluzioni più vecchie sono molto più lente (come hai notato). È stato divertente anche capirlo :-)
John Powell,

3

Se hai accesso a PostGIS 2.3, prova la nuova funzione ST_Voronoi, impegnata di recente:

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

Esistono build precompilate per Windows: http://postgis.net/windows_downloads/


Grazie per le informazioni sulla presenza della funzione ST_Voronoi incorporata, lo verificherò. Purtroppo ho bisogno di una soluzione che funzioni con le versioni PostGIS 2.1+, quindi la query @MickyT è la più vicina alle mie esigenze al momento.
DamnBack,

@ LR1234567. Questo richiede una versione particolare di GEOS? Domani ho tempo per testare 2.3 e ST_Voronoi.
John Powell,

Richiede GEOS 3.5
LR1234567 il
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.