포인트 레이어와 라인 레이어 사이의 가장 가까운 이웃? [닫은]


37

#qgis와 #postgis 사이의 stackoverflow 및 irc에 대해이 질문을 여러 번 요청했으며 실제 답변없이 postgis에서 코딩하거나 자체적으로 구현하려고했습니다.

프로그래밍 (가장 바람직하게는 파이썬)을 사용하여 점 레이어에서 선 또는 다각형 레이어의 가장 가까운 선에 대한 투영까지 선을 그립니다.

현재 대부분의 데이터는 ESRI의 형태와 postgis 형식입니다. 그러나 나는 주로 shp + qgis 사용자이기 때문에 postgis 솔루션에서 멀리 떨어져 있습니다.

이상적인 솔루션은 파이썬 또는 유사한 라이브러리로 GDAL / OGR을 구현하는 것입니다.

  • GDAL / OGR 라이브러리 사용 어디서부터 시작해야합니까? 솔루션 계획을 세우는 것이 가능할까요?
  • NetworkX를 사용하여 가장 가까운 이웃 분석을 수행 할 수 있습니까?
  • 이것이 실제로 가능합니까?

더 쉬운 경우, 점이 투영 된 점 대신 세그먼트 끝점에 연결될 수 있습니다


선이 선 세그먼트에 직교하는 것으로 제한 될 수 있습니까?
WolfOdrade

@wolfOdrade-전체적으로는 중요하지 않습니다.
dassouki

답변:


33

이 질문은 내가 생각했던 것보다 조금 까다로 웠습니다. (GEOS로부터 의) Shapely 제공 거리 와 같은 최단 거리 자체의 구현이 많이 있습니다 . 그러나 교차점 자체를 제공하는 솔루션은 거의 없지만 거리 만 있습니다.

첫 번째 시도는 점과 다각형 사이의 거리만큼 점을 버퍼링하고 교차점을 찾았지만 반올림 오류로 인해 정확한 답을 얻을 수 없습니다.

다음은 이러한 방정식을 기반으로 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을 사용하는 Nicklas Avén의 솔루션이 가장 빠릅니다.
mlt

맞습니다 . 파이썬에서 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

이것은 조금 낡았지만 오늘이 문제에 대한 해결책을 찾고있었습니다 (point-> line). 이 관련 문제에 대해 내가 찾은 가장 간단한 해결책은 다음과 같습니다.

>>> 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는 폴리 라인의 정점을 폴링 한 다음 표준 방법을 통해 거리를 계산할 수 있습니다. 예를 들어, 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 스크립트입니다. 합리적인 양의 점과 선에 적합합니다. 그러나 나는 엄청난 양의 물건으로 시도하지 않았습니다.

물론 유휴 상태이거나 다른 "파이썬 솔루션"으로 복사하여 "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값은 가장 가까운 이웃이 반환되는 횟수를 설정합니다. 실제로 각 점은 각 선 객체에 가능한 가장 짧은 거리에 투영되어야합니다. 그리고 발견 된 최소 거리는 우리가 찾는 가장 가까운 이웃으로서 정확한 선과 투사점을 제공 할 것입니다. 루핑 시간을 줄이기 위해 가장 가까운 Neighbor 명령이 사용됩니다. 선택 counterSelec값이 1로 감소하는 것은 만났다 "첫 번째"객체를 (더 정확히 경계 상자 것) 반환하고 그것은 바로 하나하지 않을 수 있습니다. 가장 짧은 거리를 결정하기 위해 서로 다른 선 크기 객체를 선택해야 할 수도 있고 3 또는 5 일 수도 있고 가장 가까운 객체 일 수도 있습니다. 값이 클수록 시간이 오래 걸립니다. 수백 개의 포인트와 라인으로 3 또는 5 개의 가장 가까운 이웃에서 매우 느리게 시작합니다. 수천으로 인해 이러한 값으로 버그가 발생할 수 있습니다.


3

관심사 및 사용 사례에 따라 "맵 일치 알고리즘"을 살펴 보는 것이 유용 할 수 있습니다. 예를 들어, OSM 위키에는 http://wiki.openstreetmap.org/wiki/Roadmatcher 와 같은 RoadMatcher 프로젝트가 있습니다.


여행 수요 및 예측을위한 것입니다. 일반적으로 영역을 교통 분석 영역 (폴리곤)으로 나누고 해당 영역에있는 모든 트래픽의 "더미"생성자로 다각형의 중심을 설정합니다. 그런 다음 해당 지점에서 가장 가까운 도로까지 x 또는 y "더미 도로 링크"선을 그리고 해당 구역에서 해당 더미 링크와 실제 도로 층으로 동일하게 트래픽을 분배합니다.
dassouki

아, 그래서 당신의 목표는이 "더미로드 링크"의 생성을 자동화하는 것입니까?
underdark

실제로 :) 또는 더미 링크
dassouki
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.