Construyendo diagrama de Voronoi en PostGIS

12

Estoy tratando de construir el diagrama voronoi a partir de la cuadrícula de puntos usando el código modificado de aquí . Esta es una consulta SQL después de mis modificaciones:

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

Abajo: resultado de mi consulta. ingrese la descripción de la imagen aquí

Como puede ver, obtengo un diagrama de voronoi "casi" correcto, excepto los puntos resaltados que no tienen un polígono único. A continuación se muestra lo que produce el algoritmo QGIS y lo que me gustaría obtener de la consulta. ¿Alguna sugerencia donde está el problema con mi código?

ingrese la descripción de la imagen aquí

DamnBack
fuente
Quizás también pueda comparar el resultado de la función SpatiaLite " VoronojDiagram " gaia-gis.it/gaia-sins/spatialite-sql-latest.html y echar un vistazo al código fuente en gaia-gis.it/fossil/libspatialite/ índice .
user30184
Buena pregunta, he estado mirando la misma pregunta a la que hace referencia, con miras a acelerarla, pero se me acaba el tiempo. No estaba al tanto de este problema con los puntos externos.
John Powell
55
Por lo que vale, tenemos ST_Voronoi en PostGIS 2.3, Dan Baston pronto revisará el código para ello: trac.osgeo.org/postgis/ticket/2259 parece bastante hecho, solo necesita ser introducido. Sería genial tenerlo. prueba de personas
LR1234567
¿Puedes publicar el conjunto de puntos que estás usando? Me importaría hacer un poco de prueba en esto yo mismo
MickyT
@MickyT Aquí hay un enlace a mis datos. Data SRID es un 2180.
DamnBack

Respuestas:

6

Si bien esto soluciona el problema inmediato con la consulta de los datos en cuestión, no estoy contento con esto como una solución para el uso general y volveré a visitar esta y la respuesta anterior cuando pueda.

El problema era que la consulta original estaba usando un casco convexo en los bordes voronoi para determinar el borde exterior del polígono voronoi. Esto significaba que algunos de los bordes del voronoi no estaban llegando al exterior cuando deberían haberlo estado. Miré el uso de la funcionalidad del casco cóncavo, pero realmente no funcionó como quería.
Como solución rápida, he cambiado el casco convexo para que se construya alrededor del conjunto cerrado de bordes voronoi más un búfer alrededor de los bordes originales. Los bordes de voronoi que no se cierran se extienden una gran distancia para tratar de asegurarse de que se cruzan con el exterior y se utilizan para construir polígonos. Es posible que desee jugar con los parámetros del búfer.

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;
MickyT
fuente
¡Gracias por la explicación y la solución rápida del problema! Funciona con mis datos (un poco más lento debido a ST_Union(ST_Buffer(geom))), pero continuaré probando con otros conjuntos de puntos. Mientras tanto esperaré como dijiste: una solución más general. :)
DamnBack
¿Tiene una imagen que puede publicar en su salida final?
Jeryl Cook
10

Siguiendo una sugerencia de @ LR1234567 para probar la nueva funcionalidad ST_Voronoi que ha desarrollado @dbaston, la sorprendente respuesta original de @MickyT (como se indica en la pregunta de OP) y el uso de los datos originales ahora se puede simplificar para:

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

lo que da como resultado esta salida, idéntica a la pregunta del OP.

ingrese la descripción de la imagen aquí

Sin embargo, esto sufre el mismo problema, ahora corregido en la respuesta de MickyT, que los puntos en el casco cóncavo no obtienen un polígono cerrado (como se esperaría del algoritmo). Solucioné este problema con una consulta con la siguiente serie de pasos.

  1. Calcule el casco cóncavo de los puntos de entrada: los puntos en el casco cóncavo son aquellos que tienen polígonos ilimitados en el diagrama de salida de Voronoi.
  2. Encuentre los puntos originales en el casco cóncavo (puntos amarillos en el diagrama 2 a continuación).
  3. Proteger el casco cóncavo (¿la distancia del almacenamiento intermedio es arbitraria y quizás podría encontrarse de manera más óptima a partir de los datos de entrada?).
  4. Encuentre los puntos más cercanos en el búfer del casco cóncavo más cercano a los puntos en el paso 2. Estos se muestran en verde en el diagrama a continuación.
  5. Agregue estos puntos al conjunto de datos original
  6. Calcule el diagrama de Voronoi de este conjunto de datos combinado. Como se puede ver en el tercer diagrama, los puntos en el casco ahora tienen polígonos cerrados.

Diagrama 2 que muestra los puntos en el casco cóncavo (amarillo) y los puntos más cercanos al amortiguador en el casco (verde). Diagrama 2.

La consulta obviamente podría simplificarse / comprimirse, pero lo dejé es esta forma como una serie de CTE, ya que es más fácil seguir los pasos secuencialmente de esa manera. Esta consulta se ejecuta en el conjunto de datos original en milisegundos (promedio de 11 ms en un servidor de desarrollo), mientras que la respuesta de MickyT usando ST_Delauney se ejecuta en 4800 ms en el mismo servidor. DBaston afirma que se puede obtener otro aumento de velocidad de orden de magnitud construyendo contra el tronco actual de GEOS, 3.6dev, debido a mejoras en las rutinas de triangulación.

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;

Diagrama 3 que muestra todos los puntos ahora encerrados en un polígono diagrama 3

Nota: Actualmente, ST_Voronoi implica construir Postgis desde la fuente (versión 2.3 o troncal) y vincularlo con GEOS 3.5 o superior.

Editar: Acabo de ver Postgis 2.3 ya que está instalado en Amazon Web Services, y parece que el nombre de la función ahora es ST_VoronoiPolygons.

Sin duda, esta consulta / algoritmo podría mejorarse. Sugerencias de bienvenida.

John Powell
fuente
@dbaston. ¿Se pregunta si tiene algún comentario sobre este enfoque?
John Powell
1
así, todos los puntos do obtener un polígono que encierra, es sólo que es desproporcionadamente grande. Si y cómo deberían hacerse más pequeños es bastante subjetivo, y sin saber exactamente lo que se desea para los polígonos exteriores, es difícil saber cuál es la "mejor" forma. El tuyo me parece un buen método. He utilizado un método menos sofisticado que es similar en espíritu al suyo, dejando caer puntos adicionales a lo largo de un límite de casco convexo amortiguado en un espacio fijo determinado por la densidad del punto promedio.
dbaston
@dbaston. Gracias, solo asegurándome de que no me faltaba algo obvio. Un algoritmo para reducir los polígonos externos a algo más en línea con el tamaño de los internos (en mi caso, áreas de código postal) es algo en lo que tendré que pensar un poco más.
John Powell
@ John Barça Gracias por otra gran solución. La velocidad de los cálculos es más que satisfactoria con este enfoque. Desafortunadamente, me gustaría usar este algoritmo dentro de mi complemento QGIS, y tiene que funcionar con PostGIS 2.1+ listo para usar. Pero seguramente usaré esta solución después del lanzamiento oficial de PostGIS 2.3. De todos modos, gracias por respuestas tan completas. :)
DamnBack
@DamnBack. De nada. Necesitaba esto para el trabajo y su pregunta realmente me ayudó mucho, ya que no tenía idea de que saldría ST_Voronoi, y las soluciones más antiguas son mucho más lentas (como habrán notado). Fue divertido descubrirlo también :-)
John Powell
3

Si tiene acceso a PostGIS 2.3, pruebe la nueva función ST_Voronoi, recientemente comprometida:

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

Hay compilaciones precompiladas para Windows: http://postgis.net/windows_downloads/

LR1234567
fuente
Gracias por la información de que hay una próxima función ST_Voronoi incorporada. Lo comprobaré. Desafortunadamente, necesito una solución que funcione en versiones PostGIS 2.1+, por lo que la consulta @MickyT es la más cercana a mis necesidades en este momento.
DamnBack
@ LR1234567. ¿Requiere esto alguna versión particular de GEOS? Mañana tengo tiempo para probar 2.3 y ST_Voronoi.
John Powell
Requiere GEOS 3.5
LR1234567