具有ST_Distance,kNN的PostGIS最近点


23

我需要在一个表上的每个元素上获取另一个表的最接近点。第一个表包含交通标志,第二个表包含城镇的入口大厅。事实是我不能使用ST_ClosestPoint函数,而必须使用ST_Distance函数并获取min(ST_distance)记录,但是我非常想建立查询。

CREATE TABLE traffic_signs
(
  id numeric(8,0) ),
  "GEOMETRY" geometry,
  CONSTRAINT traffic_signs_pkey PRIMARY KEY (id),
  CONSTRAINT traffic_signs_id_key UNIQUE (id)
)
WITH (
  OIDS=TRUE
);

CREATE TABLE entrance_halls
(
  id numeric(8,0) ),
  "GEOMETRY" geometry,
  CONSTRAINT entrance_halls_pkey PRIMARY KEY (id),
  CONSTRAINT entrance_halls_id_key UNIQUE (id)
)
WITH (
  OIDS=TRUE
);

我需要获取每个traffic_sign中最接近的entrnce_hall的ID。

到目前为止我的查询:

SELECT senal.id,port.id,ST_Distance(port."GEOMETRY",senal."GEOMETRY")  as dist
    FROM traffic_signs As senal, entrance_halls As port   
    ORDER BY senal.id,port.id,ST_Distance(port."GEOMETRY",senal."GEOMETRY")

有了这个,我得到了每个交通标志到每个入口大厅的距离。但是,如何才能获得最小距离?

问候,


什么版本的PostgreSQL?
2015年

Answers:


41

你快到了。有一个小技巧是使用Postgres的distinct运算符,该运算符将返回每种组合的第一个匹配项-当您通过ST_Distance进行订购时,它将有效地将每个参量中的最接近点返回到每个端口。

SELECT 
   DISTINCT ON (senal.id) senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY")  as dist
FROM traffic_signs As senal, entrance_halls As port   
ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");

如果您知道每种情况下的最小距离不超过x的某个值(并且表上有空间索引),则可以通过添加一个来加快速度WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance),例如,如果已知所有最小距离为不超过10公里,则:

SELECT 
   DISTINCT ON (senal.id) senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY")  as dist
FROM traffic_signs As senal, entrance_halls As port  
WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", 10000) 
ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");

显然,这需要谨慎使用,因为最小距离越大,您将无法获得针对该传感器和端口的组合的任何信息。

注意:按顺序排序必须匹配顺序上的不重复,这很有意义,因为不重复是基于某种排序获取第一个不同的组。

假定您在两个表上都有一个空间索引。

编辑1。还有另一个选择,就是使用Postgres的<->和<#>运算符(分别计算中心点和边界框距离),它们可以更有效地利用空间索引,并且不需要ST_DWithin hack来避免n ^ 2个比较。有一篇很好的博客文章解释了它们如何工作。通常要注意的是,这两个运算符在ORDER BY子句中工作。

SELECT senal.id, 
  (SELECT port.id 
   FROM entrance_halls as port 
   ORDER BY senal.geom <#> port.geom LIMIT 1)
FROM  traffic_signs as senal;

编辑2。由于这个问题已经引起了广泛关注,并且k最近邻(kNN)通常是GIS中的一个难题(就算法运行时间而言),因此似乎有必要在此问题的原始范围上进行扩展。

查找一个对象的x个最近邻居的标准方法是使用LATERAL JOIN(概念上类似于每个循环的a)。从dbaston的回答中毫不客气借钱,您将执行以下操作:

SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
 FROM traffic_signs
CROSS JOIN LATERAL 
  (SELECT
      id, 
      ST_Distance(ports.geom, signs.geom) as dist
      FROM ports
      ORDER BY signs.geom <-> ports.geom
     LIMIT 1
   ) AS closest_port

因此,如果要查找最接近的10个端口(按距离排序),只需更改横向子查询中的LIMIT子句。没有LATERAL JOINS,这将很难完成,并且涉及到使用ARRAY类型的逻辑。尽管这种方法行之有效,但如果您只需要搜索给定的距离,则可以大大加快速度。在这种情况下,您可以在子查询中使用ST_DWithin(signs.geom,ports.geom,1000),这是由于使用<->运算符进行索引的方式所致,其中一种几何形状应该是常量,而不是a列参考-可能要快得多。因此,例如,要获取10公里以内的3个最近的端口,您可以编写以下内容。

 SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
 FROM traffic_signs
CROSS JOIN LATERAL 
  (SELECT
      id, 
      ST_Distance(ports.geom, signs.geom) as dist
      FROM ports
      WHERE ST_DWithin(ports.geom, signs.geom, 10000)
      ORDER BY ST_Distance(ports.geom, signs.geom)
     LIMIT 3
   ) AS closest_port;

与往常一样,用法会因您的数据分布和查询而异,因此EXPLAIN是您最好的朋友。

最后,如果使用LEFT而不是CROSS JOIN LATERAL,则有一个小问题,因为您必须在横向查询别名之后添加ON TRUE,例如,

SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
 FROM traffic_signs
LEFT JOIN LATERAL 
  (SELECT
      id, 
      ST_Distance(ports.geom, signs.geom) as dist
      FROM ports          
      ORDER BY signs.geom <-> ports.geom
      LIMIT 1
   ) AS closest_port
   ON TRUE;

应该注意的是,这在处理大量数据时效果不佳。
2015年

@JakubKania。这取决于您是否可以使用ST_DWithin。但是,是的,观点很明确。不幸的是,按<-> / <#>运算符的Order要求其中一个几何是常数,不是吗?
约翰·鲍威尔,

@JohnPowellakaBarça,您是否有机会知道该博客帖子如今的住处?-还是<->和<#>运算符的类似解释?谢谢!!
DPSSpatial

@DPSSpatial,这很烦人。我不这样做,但这个这个其中讨论一下这种方法。第二个也使用横向连接,这是另一个有趣的增强。
约翰·鲍威尔,

@DPSSpatial。这些<->,<#>和横向连接的东西都有点滑。我已经使用非常大的数据集进行了此操作,并且在没有使用ST_DWithin的情况下性能非常糟糕,所有这些都应该避免。最终,knn是一个复杂的问题,因此用法可能会有所不同。祝你好运:-)
John Powell

13

这可以LATERAL JOIN在PostgreSQL 9.3+中完成:

SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
FROM traffic_signs
CROSS JOIN LATERAL 
  (SELECT
     id, 
     ST_Distance(ports.geom, signs.geom) as dist
     FROM ports
     ORDER BY signs.geom <-> ports.geom
   LIMIT 1) AS closest_port

10

带有交叉联接的方法不使用索引,并且需要大量内存。因此,您基本上有两种选择。在9.3之前的版本中,您将使用相关子查询。9.3+您可以使用LATERAL JOIN

KNN GIST发生横向扭曲即将进入您附近的数据库

(确切的查询将很快跟进)


1
酷使用横向连接。在这种情况下以前从未见过。
约翰·鲍威尔,

1
@JohnBarça这是我见过的最好的上下文之一。我还怀疑当您确实需要使用它ST_DISTANCE()来查找最近的多边形并且交叉连接导致服务器内存不足时,这将很有用。最近的多边形查询仍未解决AFAIK。
雅各布·卡尼亚

2

@约翰·巴萨

ORDER BY是错误的!

ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");

senal.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY"),port.id;

否则,它不会返回最接近的端口号,而只会返回端口号很小的端口号


1
正确的代码如下所示(我使用了点和线):SELECT DISTINCT ON (points.id) points.id, lines.id, ST_Distance(lines.geom, points.geom) as dist FROM development.passed_entries As points, development."de_muc_rawSections_cleaned" As lines ORDER BY points.id, ST_Distance(lines.geom, points.geom),lines.id;
blackgis

1
好吧,我现在得到你。实际上,最好使用LATERAL JOIN方法,就像@dbaston的答案一样,它可以清楚地表明正在比较什么东西和其他东西。我不再使用上面的方法。
John Powell
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.