在PostGIS中构建Voronoi图


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

很好的问题,我一直在看您引用的同一问题,目的是加快速度,但要花很多时间。我不了解外在问题。
约翰·鲍威尔

5
为了使PostGIS 2.3中包含ST_Voronoi有价值,Dan Baston很快将为其签入代码-trac.osgeo.org/postgis/ticket/2259 看起来已经完成了很多工作,只需要拉进去。乡亲测试
LR1234567

您可以张贴您正在使用的点集吗?我介意对此做一些测试
MickyT 2015年

@MickyT这是指向 我的数据的链接。数据SRID是2180
DamnBack

Answers:


6

虽然这解决了有关数据查询的即时问题,但我不满意将其作为一般用途的解决方案,因此,我将在可能的情况下重新讨论该问题和上一个答案。

问题在于,原始查询是在voronoi边缘上使用凸包来确定voronoi多边形的外部边缘。这意味着某些voronoi边缘本来没有到达外部。我看过使用凹面船体功能,但是它并没有真正按我的意愿工作。
作为快速解决方案,我已将凸包更改为围绕封闭的voronoi边缘集构建,并在原始边缘周围添加了缓冲区。不会闭合的voronoi边缘延伸了很长的距离,以尝试确保它们与外部相交并用于构建多边形。您可能想玩一下缓冲区参数。

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

您是否可以在最终输出中发布图片?
Jery​​l Cook

10

在@ LR1234567建议尝试使用@dbaston开发的新ST_Voronoi功能之后,@ 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图。从第三张图中可以看出,船体上的点现在具有封闭的多边形。

图2显示了凹壳上的点(黄色)和最接近缓冲区的点(绿色)。 图2。

该查询显然可以简化/压缩,但我将其作为一系列CTE保留了此形式,因为这样可以更轻松地按顺序执行这些步骤。此查询以毫秒为单位在原始数据集上运行(在开发服务器上平均为11ms),而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涉及从源(2.3版或主干)构建Postgis,并与GEOS 3.5或更高版本链接。

编辑:我刚刚看过Postgis 2.3,因为它已安装在Amazon Web Services上,并且似乎函数名称现在为ST_VoronoiPolygons。

毫无疑问,此查询/算法可以得到改进。欢迎提出建议。


@dbaston。想知道您对此方法有何评论?
约翰·鲍威尔

1
好吧,所有的点得到了一个封闭的多边形,只是它的大小不成比例。是否以及如何将其缩小是很主观的,并且在不确切知道外部多边形需要什么的情况下,很难知道什么是“最佳”方法。在我看来,您的方法不错。我使用了一种与您的精神类似的不太复杂的方法,沿着缓冲的凸包边界以平均点密度确定的固定间距放置多余的点。
dbaston

@dbaston。谢谢,只是确保我没有错过任何明显的东西。我将需要更多地考虑将外部多边形缩小到与内部多边形的大小更加一致的算法(在我的情况下是邮政编码区域)。
约翰·鲍威尔

@JohnBarç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

有针对Windows的预编译版本-http: //postgis.net/windows_downloads/


感谢您提供即将推出的内置ST_Voronoi函数的信息-我将对其进行检查。不幸的是,我需要适用于PostGIS 2.1+版本的解决方案,因此,@ MickyT查询是目前最符合我需求的解决方案。
DamnBack 2015年

@ LR1234567。这是否需要任何特定版本的GEOS。我明天有时间测试2.3和ST_Voronoi。
John Powell

需要GEOS 3.5
LR1234567
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.