在PostGIS中特定距离的点之间画线?


9

我有沿着街道的点的数据,我想将这些点变成简单的彩色线。有什么指针可以解决这个问题,或者有什么算法可以帮助我解决这个问题? 我想把这条街上的点变成线。

我希望使用PostGIS函数来执行此操作,但是我愿意接受建议,这是来自.shp文件的数据。

Edit1:更新了图片以演示此问题的理想解决方案。

画线纯粹是基于这些点之间的距离,没有别的可用于分组的依据。理想情况下,这将是沿投影线的最大指定距离处的点吗?通过投影线,我的意思是找到第一个点,然后找到最靠近它的下一个,然后投影一条线,并检查该线上是否有任何点与该线上已经存在的任何点具有最大距离。


1
您打算使用哪种软件?
ArMoraer '16

您是否想将这些变成人行道?
DPSSpatial '16

我希望使用PostGIS函数来执行此操作,但我愿意接受建议,这是来自.shp文件的数据。
Mahakala

1
您能确切显示要在工程图或另一工程图上连接的点吗?一次只有两点吗?还是三个?应该连接的点之间的距离是始终相同还是“仅”在某个阈值以下?
PeterHorsbøllMøller'16

1
非常感谢@dbaston和MarHoff,我要等到四月下旬才有时间测试您的想法,我希望我能将赏金分配给您,但我需要将其奖励给您中的一位,dbaston也给了我一些疑问所以我会接受他的回应。感谢所有花时间回答的人!伟大的社区将成为:-)的一部分
Mahakala

Answers:


8

您可以使用递归查询从要构建的线的每个检测到的端点开始探索每个点的最近邻居。

先决条件:准备一个包含您的点的postgis图层,以及另一个包含一个包含您的道路的Multi-linestring对象的图层。两层必须在同一CRS上。这是我创建的测试数据集的代码,请根据需要对其进行修改。(在postgres 9.2和postgis 2.1上测试)

WITH RECURSIVE
points as (SELECT id, st_transform((st_dump(wkb_geometry)).geom,2154) as geom, my_comment as com FROM mypoints),
roads as (SELECT st_transform(ST_union(wkb_geometry),2154) as geom from highway),

在此处输入图片说明

步骤如下:

  1. 为每个点生成满足这三个条件的每个邻居及其距离的列表。

    • 距离不得超过用户定义的阈值(这将避免链接到孤立点) 在此处输入图片说明
      graph_full as (
      SELECT a.id, b.id as link_id, a.com, st_makeline(a.geom,b.geom) as geom, st_distance(a.geom,b.geom) as distance
      FROM points a
      LEFT JOIN points b ON a.id<>b.id
      WHERE st_distance(a.geom,b.geom) <= 15
      ),
      
    • 直路一定不能过马路 在此处输入图片说明
      graph as (
      SELECt graph_full.*
      FROM graph_full RIGHT JOIN
      roads ON st_intersects(graph_full.geom,roads.geom) = false
      ),
      
    • 距离不得超过用户定义的距最近邻居的距离的比率(与固定距离相比,它应能更好地适应不规则数字化) 此部分实际上太难实现,坚持固定的搜索半径

    我们将此表称为“图形”

  2. 通过连接到图形并仅保留图形中仅具有一个条目的点来选择线终点。 在此处输入图片说明

    eol as (
    SELECT points.* FROM
    points  JOIN
    (SELECT id, count(*) FROM graph 
    GROUP BY id
    HAVING count(*)= 1) sel
    ON points.id = sel.id),
    

    我们将此表称为“ eol”(行尾)
    容易吗?做一个很棒的图但坚持下去的东西的奖励将在下一步变得疯狂

  3. 设置一个递归查询,该查询将从每个eol开始在邻居之间循环 在此处输入图片说明

    • 使用eol表初始化递归查询,并为深度添加一个计数器,为路径添加一个聚合器以及一个用于构造线条的几何构造函数
    • 通过使用图形切换到最近的邻居并检查您是否永不后退,来进行下一次迭代
    • 迭代完成后,仅保留每个起点的最长路径(如果您的数据集包括预期线之间的潜在交点,则该零件将需要更多条件)
    recurse_eol (id, link_id, depth, path, start_id, geom) AS (--initialisation
    SELECT id, link_id, depth, path, start_id, geom FROM (
        SELECT eol.id, graph.link_id,1 as depth,
        ARRAY[eol.id, graph.link_id] as path,
        eol.id as start_id,
        graph.geom as geom,
        (row_number() OVER (PARTITION BY eol.id ORDER BY distance asc))=1 as test
        FROM eol JOIn graph ON eol.id = graph.id 
        ) foo
    WHERE test = true
    
    UNION ALL ---here start the recursive part
    
    SELECT id, link_id, depth, path, start_id, geom  FROM (
        SELECT graph.id, graph.link_id, r.depth+1 as depth,
        path || graph.link_id as path,
        r.start_id,
        ST_union(r.geom,graph.geom) as geom,
        (row_number() OVER (PARTITION BY r.id ORDER BY distance asc))=1 as test
        FROM recurse_eol r JOIN graph ON r.link_id = graph.id AND NOT graph.link_id = ANY(path)) foo
    WHERE test = true AND depth < 1000), --this last line is a safe guard to stop recurring after 1000 run adapt it as needed
    

    我们将此表称为“ recurse_eol”

  4. 对于每个起点仅保留最长的线,并删除每条精确的重复路径示例:路径1,2,3,5和5,3,2,1是同一条线,因为这是两条不同的“线尾”

    result as (SELECT start_id, path, depth, geom FROM
    (SELECT *,
    row_number() OVER (PARTITION BY array(SELECT * FROM unnest(path) ORDER BY 1))=1 as test_duplicate,
    (max(depth) OVER (PARTITION BY start_id))=depth as test_depth
    FROM recurse_eol) foo
    WHERE  test_depth = true AND test_duplicate = true)
    
    SELECT * FROM result
  5. 手动检查其余错误(孤立点,重叠线,形状怪异的街道)


按照承诺进行更新,我仍然不知道为什么有时从同一行的相反eol开始递归查询时不会给出完全相同的结果,因此到目前为止,某些重复项可能仍保留在结果层中。

随便问我,我完全明白此代码需要更多注释。这是完整的查询:

WITH RECURSIVE
points as (SELECT id, st_transform((st_dump(wkb_geometry)).geom,2154) as geom, my_comment as com FROM mypoints),
roads as (SELECT st_transform(ST_union(wkb_geometry),2154) as geom from highway),

graph_full as (
    SELECT a.id, b.id as link_id, a.com, st_makeline(a.geom,b.geom) as geom, st_distance(a.geom,b.geom) as distance
    FROM points a
    LEFT JOIN points b ON a.id<>b.id
    WHERE st_distance(a.geom,b.geom) <= 15
    ),

graph as (
    SELECt graph_full.*
    FROM graph_full RIGHT JOIN
    roads ON st_intersects(graph_full.geom,roads.geom) = false
    ),

eol as (
    SELECT points.* FROM
    points  JOIN
        (SELECT id, count(*) FROM graph 
        GROUP BY id
        HAVING count(*)= 1) sel
    ON points.id = sel.id),


recurse_eol (id, link_id, depth, path, start_id, geom) AS (
    SELECT id, link_id, depth, path, start_id, geom FROM (
        SELECT eol.id, graph.link_id,1 as depth,
        ARRAY[eol.id, graph.link_id] as path,
        eol.id as start_id,
        graph.geom as geom,
        (row_number() OVER (PARTITION BY eol.id ORDER BY distance asc))=1 as test
        FROM eol JOIn graph ON eol.id = graph.id 
        ) foo
    WHERE test = true

UNION ALL
    SELECT id, link_id, depth, path, start_id, geom  FROM (
        SELECT graph.id, graph.link_id, r.depth+1 as depth,
        path || graph.link_id as path,
        r.start_id,
        ST_union(r.geom,graph.geom) as geom,
        (row_number() OVER (PARTITION BY r.id ORDER BY distance asc))=1 as test
        FROM recurse_eol r JOIN graph ON r.link_id = graph.id AND NOT graph.link_id = ANY(path)) foo
    WHERE test = true AND depth < 1000),

result as (SELECT start_id, path, depth, geom FROM
    (SELECT *,
    row_number() OVER (PARTITION BY array(SELECT * FROM unnest(path) ORDER BY 1))=1 as test_duplicate,
    (max(depth) OVER (PARTITION BY start_id))=depth as test_depth
    FROM recurse_eol) foo
WHERE  test_depth = true AND test_duplicate = true)

SELECT * FROM result


嗨,@ MarHoff,谢谢您的回答,我有话要说。我没想到一个完整的解决方案,只是一个在哪里寻找答案的指针。我想更多地了解这一点,我将继续挖掘,以后可能还会有更多问题。我需要了解您的算法,无论如何,这将需要我一些时间:)
Mahakala

得到了一个有效的脚本,在此处预览qgiscloud.com/MarHoff/test_qgiscloud_bis仍然有一个重复数据删除的小警告...我猜没有更多的赏金,也没有更多的压力,所以我会在可能的时候发布发行版。这个难题虽然很有趣
MarHoff '16

谢谢@MarHoff,如果可以的话,我将无法分红,我看不到如何给您任何奖励,但是非常感谢您仔细研究了这一点和您的证明。看起来很真实:)
Mahakala

做完了 感谢您的困惑,也很抱歉。如果有其他答案对您有用,那么有时最好是简单的……我的答案可能是对它的想法有点过头了。尽管使用CTE +递归查询+ Windows函数+单个查询进行postgis的好例子;)
MarHoff 2016年

8

正如@FelixIP指出的那样,第一步是找到组成每一行的点。您可以通过最大间隔距离调用ST_ClusterWithin来实现:

SELECT
  row_number() OVER () AS cid, 
  (ST_Dump(geom)).geom 
FROM (
  SELECT unnest(st_clusterwithin(geom, 0.05)) AS geom 
  FROM inputs) sq

然后,您需要使用启发式方法来建立一条穿过每个群集中所有点的直线。例如,如果您可以将所需的线假定为Y单调,则可以对每个聚类中的点进行排序并将其馈入ST_MakeLine。将所有这些组合在一起看起来像这样:

SELECT 
  ST_MakeLine(geom ORDER BY ST_Y(geom)) AS geom
FROM (
  SELECT row_number() OVER () AS cid, 
  (ST_Dump(geom)).geom FROM (
    SELECT unnest(st_clusterwithin(geom, 0.05)) AS geom 
    FROM inputs) sq) ssq 
GROUP BY cid

如果数据集包含弯曲的道路,那么Y单调(甚至在X / Y单调之间切换)的方法就行不通了。是这样吗 排序算法是这个问题恕我直言的最难的部分。
MarHoff '16

@MarHoff:是的,弯曲的道路将是一个问题,但是我正在尝试使大多数数据自动转换,其余的则需要手动完成。或者,我将继续深入研究主题以找出解决方案,但它可能要比花费某人修复剩余数据花费的时间更长。我需要评估结果才能做出决定。谢谢你指出这一点!
Mahakala

Statut调整了我只想到了一个需要检查的技巧...
MarHoff 2016年

有没有什么健壮的方法可以做到这一点,而不必尝试所有可能的点排序,而是找出哪一个给出的总长度最短?
dbaston '16

如果这些点集始终沿着道路行驶,则将点的位置投影到路段上(ST_Line_Locate_Point),然后根据结果对点进行排序。
travis
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.