点层和线层之间最近的邻居?[关闭]


37

我已经在#qgis和#postgis之间的stackoverflow和irc上多次问过这个问题,我也尝试在postgis中对其进行编码或实现,但没有真正的答案。

我想使用编程(最好是python),从点层绘制一条线,使其在直线或多边形层的最近线上投影。

截至目前,我的大部分数据都采用ESRI的格式和邮政格式。但是,我宁愿远离postgis解决方案,因为我主要是shp + qgis用户。

一个理想的解决方案是用python或类似的库实现GDAL / OGR

  • 从哪里开始使用GDAL / OGR库?有可能给出解决方案吗?
  • 我可以使用NetworkX进行最近邻居分析吗?
  • 这实际上可行吗?

如果更简单,则这些点可以连接到线段终点,而不是投影点


可以将线限制为与线段正交吗?
WolfOdrade

@wolfOdrade-总体而言,没关系。
dassouki

Answers:


33

结果这个问题比我认为正确的要棘手。最短距离本身有多种实现方式,例如Shapely提供的距离(距GEOS)。但是,很少有解决方案提供交点本身,但仅提供距离。

我的第一次尝试是通过点与多边形之间的距离来缓冲该点,并寻找相交点,但是舍入错误会阻止它给出确切的答案。

这是基于以下等式的使用Shapely的完整解决方案:

#!/usr/bin/env python
from shapely.geometry import Point, Polygon
from math import sqrt
from sys import maxint

# define our polygon of interest, and the point we'd like to test
# for the nearest location
polygon = Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
point = Point(0.5, 1.5)

# pairs iterator:
# http://stackoverflow.com/questions/1257413/1257446#1257446
def pairs(lst):
    i = iter(lst)
    first = prev = i.next()
    for item in i:
        yield prev, item
        prev = item
    yield item, first

# these methods rewritten from the C version of Paul Bourke's
# geometry computations:
# http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/
def magnitude(p1, p2):
    vect_x = p2.x - p1.x
    vect_y = p2.y - p1.y
    return sqrt(vect_x**2 + vect_y**2)

def intersect_point_to_line(point, line_start, line_end):
    line_magnitude =  magnitude(line_end, line_start)
    u = ((point.x - line_start.x) * (line_end.x - line_start.x) +
         (point.y - line_start.y) * (line_end.y - line_start.y)) \
         / (line_magnitude ** 2)

    # closest point does not fall within the line segment, 
    # take the shorter distance to an endpoint
    if u < 0.00001 or u > 1:
        ix = magnitude(point, line_start)
        iy = magnitude(point, line_end)
        if ix > iy:
            return line_end
        else:
            return line_start
    else:
        ix = line_start.x + u * (line_end.x - line_start.x)
        iy = line_start.y + u * (line_end.y - line_start.y)
        return Point([ix, iy])

nearest_point = None
min_dist = maxint

for seg_start, seg_end in pairs(list(polygon.exterior.coords)[:-1]):
    line_start = Point(seg_start)
    line_end = Point(seg_end)

    intersection_point = intersect_point_to_line(point, line_start, line_end)
    cur_dist =  magnitude(point, intersection_point)

    if cur_dist < min_dist:
        min_dist = cur_dist
        nearest_point = intersection_point

print "Closest point found at: %s, with a distance of %.2f units." % \
   (nearest_point, min_dist)

对于后代来说,这个ArcView扩展看起来很好地解决了这个问题,但是在用死语言编写的死平台上实在是太糟糕了……


1
我想知道是否有一种方法可以索引多边形点以避免显式枚举...
mlt

@mlt不确定确切的想法,但是有些方法可以根据几何图形提供帮助。如果性能存在问题,可以进行一些基本的射线投射以确定相关的最近段。从这个角度来看,将其移入C或Pyrex可以改善性能。
scw

我的意思pairs是算法上是O(n)之类的。@eprand解决方案也许可以修改为使用KNN,但是到目前为止,我设法在没有PostGIS的情况下生活了……
mlt

我不能再编辑以前的评论了:(如果选择PostGIS,也许用ST_Closestpoint和ST_Shortestline解决NicklasAvén的方法是最快的
。– mlt

正确,您可以直接在Python中使用KNN算法。我不相信ST_Shortestline使用KNN,它只是迭代以及根据我的阅读postgis.refractions.net/documentation/postgis-doxygen/d1/dbf/...
SCW

8

PostGIS答案(对于多线串,如果为线串,请删除st_geometryn函数)

select t2.gid as point_gid, t1.gid as line_gid, 
st_makeline(t2.geom,st_line_interpolate_point(st_geometryn(t1.geom,1),st_line_locate_point(st_geometryn(t1.geom,1),t2.geom))) as geom
from your_line_layer t1, your_point_layer t2, 
(
select gid as point_gid, 
(select gid 
from your_line_layer
order by st_distance(your_line_layer.geom, your_point_layer.geom)
limit 1 ) as line_gid
from your_point_layer
) as t3
where t1.gid = t3.line_gid
and t2.gid = t3.point_gid

4

这有点旧,但是我今天正在寻找解决这个问题的方法(点->行)。针对此相关问题,我遇到的最简单的解决方案是:

>>> from shapely.geometry import Point, LineString
>>> line = LineString([(0, 0), (1, 1), (2, 2)])
>>> point = Point(0.3, 0.7)
>>> point
POINT (0.3000000000000000 0.7000000000000000)
>>> line.interpolate(line.project(point))
POINT (0.5000000000000000 0.5000000000000000)

4

如果我理解正确,那么您所要求的功能是在PostGIS中构建的。

要获得在线上投影的点,可以使用ST_Closestpoint(在PostGIS 1.5上)

您可以在此处阅读有关如何使用它的一些提示:http : //blog.jordogskog.no/2010/02/07/how-to-use-the-new-distance-related-functions-in-postgis-part1/

例如,也可以在多边形上找到与另一个多边形最接近的点。

如果要在两个几何上的两个最接近点之间建立直线,可以使用ST_Shortestline。ST_Closestpoint是ST_Shortestline中的第一点

两个几何之间的ST_Shortestline的长度与两个几何之间的ST_Distance相同。


3

请参阅以下有关我的答案不应被视为可靠解决方案的评论...我将在此保留原始帖子,以便其他人可以检查问题。

如果我理解这个问题,则此常规过程应该可行。

在欧几里得空间中找到一个点(由x,y或x,y,z定义)和一个多义线(由x,y或x,y,z的连接集合定义)之间的最短路径:

1)从给定的用户定义点(我将其称为pt0),找到折线(pt1)的最近顶点。OGRinfo可以轮询折线的顶点,然后可以通过标准方法进行距离计算。例如,对距离calc进行迭代,例如:distance_in_radians = 2 * math.asin(math.sqrt(math.pow((math.sin((pt0_radians-ptx_radians)/ 2)),2)+ math.cos(pt0_radians) * math.cos(ptx_radians)* math.pow((math.sin((pt0_radians-ptx_radians)/ 2),2)))

2)存储相关的最小距离值(d1)和(pt1)

3)查看远离pt1的两个线段(在ogrinfo线串中,这些线段将是先前和随后的顶点)。记录这些顶点(n2和n3)。

4)为每个细分创建y = mx + b公式

5)对于这两个公式中的每一个,将点(pt0)与垂直线相关联

6)计算距离和交点(d2和d3; pt2,pt3)

7)比较三个距离中的最短距离(d1,d2,d3)。您到关联节点(pt1,pt2或pt3)的pt0是最短的链接。

那是意识答案的源头-希望我对问题和解决方案的心理看法合适。


一般来说,这是行不通的。例如,点=(1,1),线=((0,2),(0,3),(3,0),(2,0))。如果您进行素描,则可以看到线上的“最近”顶点与经过最接近该点的线段不相邻...我认为处理此问题的唯一方法是检查每个线段(可能使用包围盒以避免对其进行优化)。HTH。
汤姆(Tom)

3

这是QGIS> 2.0的python脚本,由上面给出的提示和解决方案组成。对于合理数量的点和线,它可以正常工作。但是我没有尝试使用大量对象。

当然,必须将其复制到空闲状态或其他“ Python解决方案”中,然后将其保存为“ closest.point.py”。

在QGIS工具箱中,找到脚本,工具,添加一个脚本,然后选择它。

##Vector=group
##CLosest_Point_V2=name
##Couche_de_Points=vector
##Couche_de_Lignes=vector

"""
This script intent to provide a count as for the SQL Funciton CLosestPoint
Ce script vise a recréer dans QGIS la Focntion SQL : CLosest Point
It rely on the solutions provided in "Nearest neighbor between a point layer and a line layer"
  http://gis.stackexchange.com/questions/396/nearest-pojected-point-from-a-point-                               layer-on-a-line-or-polygon-outer-ring-layer
V2 du  8 aout 2016
jean-christophe.baudin@onema.fr
"""
from qgis.core import *
from qgis.gui import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import os
import sys
import unicodedata 
from osgeo import ogr
from math import sqrt
from sys import maxint

from processing import *

def magnitude(p1, p2):
    if p1==p2: return 1
    else:
        vect_x = p2.x() - p1.x()
        vect_y = p2.y() - p1.y()
        return sqrt(vect_x**2 + vect_y**2)

def intersect_point_to_line(point, line_start, line_end):
    line_magnitude =  magnitude(line_end, line_start)
    u = ((point.x()-line_start.x())*(line_end.x()-line_start.x())+(point.y()-line_start.y())*(line_end.y()-line_start.y()))/(line_magnitude**2)
    # closest point does not fall within the line segment, 
    # take the shorter distance to an endpoint
    if u < 0.0001 or u > 1:
        ix = magnitude(point, line_start)
        iy = magnitude(point, line_end)
        if ix > iy:
            return line_end
        else:
            return line_start
    else:
        ix = line_start.x() + u * (line_end.x() - line_start.x())
        iy = line_start.y() + u * (line_end.y() - line_start.y())
        return QgsPoint(ix, iy)

layerP = processing.getObject(Couche_de_Points)
providerP = layerP.dataProvider()
fieldsP = providerP.fields()
inFeatP = QgsFeature()

layerL = processing.getObject(Couche_de_Lignes)
providerL = layerL.dataProvider()
fieldsL = providerL.fields()
inFeatL = QgsFeature()

counterP = counterL= nElement=0

for featP in layerP.selectedFeatures():
    counterP+=1
if counterP==0:
    QMessageBox.information(None,"information:","Choose at least one point from point layer_"+ str(layerP.name())) 

indexLine=QgsSpatialIndex()
for featL in layerL.selectedFeatures():
    indexLine.insertFeature(featL)
    counterL+=1
if counterL==0:
    QMessageBox.information(None,"information:","Choose at least one line from point layer_"+ str(layerL.name()))
    #QMessageBox.information(None,"DEBUGindex:",str(indexBerge))     
ClosestP=QgsVectorLayer("Point", "Projected_ Points_From_"+ str(layerP.name()), "memory")
QgsMapLayerRegistry.instance().addMapLayer(ClosestP)
prClosestP = ClosestP.dataProvider()

for f in fieldsP:
    znameField= f.name()
    Type= str(f.typeName())
    if Type == 'Integer': prClosestP.addAttributes([ QgsField( znameField, QVariant.Int)])
    if Type == 'Real': prClosestP.addAttributes([ QgsField( znameField, QVariant.Double)])
    if Type == 'String': prClosestP.addAttributes([ QgsField( znameField, QVariant.String)])
    else : prClosestP.addAttributes([ QgsField( znameField, QVariant.String)])
prClosestP.addAttributes([QgsField("DistanceP", QVariant.Double),
                                        QgsField("XDep", QVariant.Double),
                                        QgsField("YDep", QVariant.Double),
                                        QgsField("XProj", QVariant.Double),
                                        QgsField("YProj", QVariant.Double),
                                        QgsField("Xmed", QVariant.Double),
                                        QgsField("Ymed", QVariant.Double)])
featsP = processing.features(layerP)
nFeat = len(featsP)
"""
for inFeatP in featsP:
    progress.setPercentage(int(100 * nElement / nFeatL))
    nElement += 1
    # pour avoir l'attribut d'un objet/feat .... 
    attributs = inFeatP.attributes()
"""

for inFeatP in layerP.selectedFeatures():
    progress.setPercentage(int(100 * nElement / counterL))
    nElement += 1
    attributs=inFeatP.attributes()
    geomP=inFeatP.geometry()
    nearest_point = None
    minVal=0.0
    counterSelec=1
    first= True
    nearestsfids=indexLine.nearestNeighbor(geomP.asPoint(),counterSelec)
    #http://blog.vitu.ch/10212013-1331/advanced-feature-requests-qgis
    #layer.getFeatures( QgsFeatureRequest().setFilterFid( fid ) )
    request = QgsFeatureRequest().setFilterFids( nearestsfids )
    #list = [ feat for feat in CoucheL.getFeatures( request ) ]
    # QMessageBox.information(None,"DEBUGnearestIndex:",str(list))
    NBNodes=0
    Dist=DistT=minValT=Distance=0.0
    for featL in  layerL.getFeatures(request):
        geomL=featL.geometry()
        firstM=True
        geomL2=geomL.asPolyline()
        NBNodes=len(geomL2)
        for i in range(1,NBNodes):
            lineStart,lineEnd=geomL2[i-1],geomL2[i]
            ProjPoint=intersect_point_to_line(geomP.asPoint(),QgsPoint(lineStart),QgsPoint(lineEnd))
            Distance=magnitude(geomP.asPoint(),ProjPoint)
            toto=''
            toto=toto+ 'lineStart :'+ str(lineStart)+ '  lineEnd : '+ str(lineEnd)+ '\n'+ '\n'
            toto=toto+ 'ProjPoint '+ str(ProjPoint)+ '\n'+ '\n'
            toto=toto+ 'Distance '+ str(Distance)
            #QMessageBox.information(None,"DEBUG", toto)
            if firstM:
                minValT,nearest_pointT,firstM = Distance,ProjPoint,False
            else:
                if Distance < minValT:
                    minValT=Distance
                    nearest_pointT=ProjPoint
            #at the end of the loop save the nearest point for a line object
            #min_dist=magnitude(ObjetPoint,PProjMin)
            #QMessageBox.information(None,"DEBUG", " Dist min: "+ str(minValT))
        if first:
            minVal,nearest_point,first = minValT,nearest_pointT,False
        else:
            if minValT < minVal:
                minVal=minValT
                nearest_point=nearest_pointT
                #at loop end give the nearest Projected points on Line nearest Line
    PProjMin=nearest_point
    Geom= QgsGeometry().fromPoint(PProjMin)
    min_dist=minVal
    PX=geomP.asPoint().x()
    PY=geomP.asPoint().y()
    Xmed=(PX+PProjMin.x())/2
    Ymed=(PY+PProjMin.y())/2
    newfeat = QgsFeature()
    newfeat.setGeometry(Geom)
    Values=[]
    #Values.extend(attributs)
    fields=layerP.pendingFields()
    Values=[attributs[i] for i in range(len(fields))]
    Values.append(min_dist)
    Values.append(PX)
    Values.append(PY)
    Values.append(PProjMin.x())
    Values.append(PProjMin.y())
    Values.append(Xmed)
    Values.append(Ymed)
    newfeat.setAttributes(Values)
    ClosestP.startEditing()  
    prClosestP.addFeatures([ newfeat ])
    #prClosestP.updateExtents()
ClosestP.commitChanges()
iface.mapCanvas().refresh()

!!! 警告 !!! 请注意,由于此行命令,可能会产生一些“怪异” /错误的投影点:

nearestsfids=indexLine.nearestNeighbor(geomP.asPoint(),counterSelec)

其中的counterSelec值设置了返回多少NearestNeighbor。实际上,每个点都应以距每个线对象尽可能短的距离投影;并且找到的最小距离将给出正确的线和投影点,作为我们寻找的最近邻居。为了减少循环时间,使用了最近的Neighbor命令。选择counterSelec减少为1 的值将返回遇到的“第一个”对象(更确切地说是边界框),并且可能不是正确的对象。为了确定最短距离,可能不得不选择3或5个不同的线大小对象,或者甚至更接近的对象。值越高,花费的时间越长。随着成百上千的点和线,与3或5个最近的邻居开始变得非常慢,而成千上万的点和线可能会与这样的值发生错误。


3

根据您的兴趣和用例,研究“地图匹配算法”可能会很有用。例如,OSM Wiki上有一个RoadMatcher项目:http ://wiki.openstreetmap.org/wiki/Roadmatcher 。


用于旅行需求和预测。通常,我们将区域划分为流量分析区域(多边形),然后将多边形的质心确定为该区域中所有流量的“虚拟”发起者。然后,我们从该点到最近的道路绘制x或y条“虚拟道路链接”线,并将来自该区域的交通平均分配到那些虚拟链接和实际道路图层上
dassouki 2010年

嗯,所以您的目标是自动创建此“虚拟道路链接”?
昏暗

确实:)或虚拟链接
dassouki 2010年
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.