¿Cómo usar ST_DelaunayTriangles para construir un diagrama de Voronoi?

13

(editar 2019) ¡ ST_VoronoiPolygons disponibles desde PostGIS v2.3 !


Con PostGIS 2.1+ podemos usar ST_DelaunayTriangles () para generar una triangulación de Delaunay , que es un gráfico dual de su diagrama de Voronoi y, en teoría, tienen una conversión exacta y reversible.

¿Existe algún script seguro de SQL estándar con un algoritmo optimizado para esta conversión de PostGIS2 Delaunay a Voronoi ?


Otras referencias: 1 , 2

Peter Krauss
fuente
¿Es gist.github.com/djq/4714788 el tipo de cosas que busca ?
MickyT
Creo que quiere una implementación puramente SQL usando ST_DelaunayTriangles ()
raphael
Vea esta respuesta para instalar ST_DelaunayTrianglesen Linux Debian Stable .
Peter Krauss
! ST_VoronoiPolygons disponibles desde PostGIS 2.3
Peter Krauss

Respuestas:

23

La siguiente consulta parece hacer un conjunto razonable de polígonos voronoi a partir de los triángulos Delaunay.

No soy un gran usuario de Postgres, por lo que probablemente pueda mejorarse bastante.

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;

Esto produce el siguiente conjunto de polígonos para los puntos de muestra incluidos en la consulta ingrese la descripción de la imagen aquí

Consulta Explicación

Paso 1

Crear los triángulos de Delaunay a partir de las geometrías de entrada

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

Paso 2

Descomponer los nodos del triángulo y hacer bordes se pueden hacer. Creo que debería haber una mejor manera de obtener los bordes, pero no encontré 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

ingrese la descripción de la imagen aquí

Paso 3

Construye los círculos circunscritos para cada triángulo y encuentra el 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

ingrese la descripción de la imagen aquí

El EdgesCTE genera cada borde y la id (ruta) del triángulo al que pertenece.

Etapa 4

'Unión externa' a la tabla 'Bordes' en sí misma, donde hay bordes iguales para diferentes triángulos (bordes interiores).

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)

Donde hay un borde común, dibuje una línea entre los respectivos centroides

ingrese la descripción de la imagen aquí

Donde el borde no está unido (exterior) dibuje una línea desde el centroide a través del centro del borde. Solo haga esto si el centroide del círculo está dentro del conjunto de triángulos.

ingrese la descripción de la imagen aquí

Paso 5

Obtenga el casco convexo para las líneas dibujadas como una línea. Unir y fusionar todas las líneas. Nodear el conjunto de líneas para que tengamos un conjunto topológico que pueda poligonizarse

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

ingrese la descripción de la imagen aquí

MickyT
fuente
Buena pista, tal vez una solución (!). Necesito probar, pero no puedo ahora ... Analizando: usted usa ST_ConvexHully en su ST_Centroidlugar "bisectrices perpendiculares" como en el algoritmo directo sugerido por mi ref1 / Kenneth Sloa ... ¿Por qué no la solución directa?
Peter Krauss
Prácticamente estoy haciendo bisectrices perpendiculares para los bordes exteriores, solo que sin todas las matemáticas :) Agregaré una explicación de los pasos que tomé para la respuesta
MickyT
Buenas ilustraciones y explicaciones, muy didácticas!   Has publicado todo lo que necesito (!), Pero en estos días no tengo Postgis2.1 para probar ... ¿Puedo verificar aquí (como comentario) algunas preguntas que cualquiera puede responder probando?   1) ST_Polygonize "crea una GeometryCollection que contiene posibles polígonos", son todas las células Voronoi, ¿correcto?   2) sobre el rendimiento, ¿cree que su solución basada en centroide tiene un tiempo de CPU similar al de "todos los cálculos matemáticos de bisectrices perpendiculares"?
Peter Krauss el
@PeterKrauss 1) ST_polygonize crea las celdas voronoi del trabajo de línea. El truco con esto es asegurarse de que todo el trabajo de línea esté dividido en los nodos. 2) No creo que haya mucha diferencia entre calcular la bisección y usar ST_Centroid en la línea. Pero necesitaría ser probado.
MickyT
Vea esta respuesta para instalar ST_DelaunayTrianglesen Linux Debian Stable .
Peter Krauss