เพื่อนบ้านที่ใกล้ที่สุดระหว่างเลเยอร์จุดและเลเยอร์บรรทัด [ปิด]


37

ฉันได้ถามคำถามนี้หลายครั้งใน stackoverflow และ irc ระหว่าง #qgis และ #postgis และฉันยังพยายามที่จะเขียนโค้ดหรือใช้มันด้วยตนเองใน postgis โดยไม่มีคำตอบจริง

โดยใช้การเขียนโปรแกรม (ส่วนใหญ่เป็นงูใหญ่) ฉันต้องการวาดเส้นจากจุดเลเยอร์ไปยังเส้นโครงของมันในบรรทัดที่ใกล้ที่สุดของเส้นหรือชั้นรูปหลายเหลี่ยม

ณ ตอนนี้ข้อมูลของฉันส่วนใหญ่อยู่ในรูปแบบของ ESRI และรูปแบบโพสต์จิส; อย่างไรก็ตามฉันควรจะอยู่ห่างจากโซลูชัน postgis เนื่องจากฉันเป็นผู้ใช้ shp + qgis

ทางออกที่ดีคือการใช้ GDAL / OGR กับ python หรือไลบรารี่ที่คล้ายกัน

  • การใช้ห้องสมุด GDAL / OGR จะเริ่มได้ที่ไหน เป็นไปได้ไหมที่จะให้แผนโซลูชัน
  • ฉันสามารถใช้ NetworkX เพื่อทำการวิเคราะห์เพื่อนบ้านที่ใกล้ที่สุดได้หรือไม่
  • เป็นไปได้จริงหรือ

หากทำได้ง่ายขึ้นคะแนนสามารถเชื่อมต่อกับจุดสิ้นสุดของกลุ่มแทนที่จะเป็นจุดที่คาดการณ์ไว้


บรรทัดสามารถถูก จำกัด ให้อยู่ในแนวตั้งฉากกับส่วนของเส้นได้หรือไม่
WolfOdrade

@wolfOdrade - โดยรวมแล้วมันไม่สำคัญ
dassouki

คำตอบ:


33

คำถามนี้กลายเป็นเรื่องยุ่งยากกว่าที่ฉันคิดไว้ก่อน มีการนำไปใช้งานของระยะทางที่สั้นที่สุดมากมายเช่นระยะทางที่จัดไว้อย่างดี(จาก 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

ฉันไม่สามารถแก้ไขความคิดเห็นก่อนหน้าของฉันอีกต่อไป :( แก้ปัญหาบางทีของ Nicklas Aven กับ ST_Closestpoint & ST_Shortestline ที่เร็วที่สุดถ้า PostGIS เป็นตัวเลือก.
mlt

ขวาคุณสามารถใช้อัลกอริทึม KNN ใน Pythonโดยตรง ผมไม่เชื่อว่า ST_Shortestline ใช้ KNN ก็แค่ iterates เช่นกันขึ้นอยู่กับการอ่านของฉันของpostgis.refractions.net/documentation/postgis-doxygen/d1/dbf/...
SCW

8

คำตอบ PostGIS (สำหรับ multilinestring ถ้า linestring ให้ลบฟังก์ชัน 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_ ระยะทางระหว่างรูปทรงเรขาคณิต


3

ดูความคิดเห็นด้านล่างเกี่ยวกับวิธีที่คำตอบของฉันไม่ควรได้รับการพิจารณาว่าเป็นทางออกที่เชื่อถือได้ ... ฉันจะออกจากโพสต์ต้นฉบับนี้เพื่อให้ผู้อื่นสามารถตรวจสอบปัญหาได้

หากฉันเข้าใจคำถามขั้นตอนทั่วไปนี้ควรใช้งานได้

ในการค้นหาเส้นทางที่สั้นที่สุดระหว่างจุด (ตามที่กำหนดโดย x, y หรือ x, y, z) และ polyine (ตามที่กำหนดโดยชุดเชื่อมต่อของ 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 linestring เหล่านี้จะเป็นจุดยอดก่อนหน้าและถัดไป) บันทึกจุดยอดเหล่านี้ (n2 และ n3)

4) สร้างสูตร y = mx + b สำหรับแต่ละส่วน

5) เชื่อมโยงจุดของคุณ (pt0) กับแนวตั้งฉากสำหรับแต่ละสูตรทั้งสองนั้น

6) คำนวณระยะทางและทางแยก (d2 และ d3; pt2, pt3)

7) เปรียบเทียบสามระยะทาง (d1, d2, d3) สำหรับระยะทางที่สั้นที่สุด pt0 ของคุณไปยังโหนดที่เกี่ยวข้อง (pt1, pt2 หรือ pt3) เป็นลิงก์ที่สั้นที่สุด

นั่นเป็นกระแสของคำตอบที่มีสติ - หวังว่าภาพในใจของฉันเกี่ยวกับปัญหาและแนวทางแก้ไขจะเหมาะสม


วิธีนี้ใช้ไม่ได้ผล เช่นจุด = (1,1), บรรทัด = ((0,2), (0,3), (3,0), (2,0)) หากคุณวาดภาพนั้นคุณสามารถเห็นจุดยอดที่ "ใกล้เคียงที่สุด" บนเส้นไม่ได้อยู่ติดกับส่วนที่ผ่านจุดที่ใกล้ที่สุด ... ฉันคิดว่าวิธีเดียวที่จะจัดการเรื่องนี้คือการตรวจสอบทุกส่วน ปรับให้เหมาะสมเล็กน้อย) HTH
Tom

3

นี่คือสคริปต์ python สำหรับ QGIS> 2.0 ทำจากคำแนะนำและวิธีแก้ปัญหาที่ให้ไว้ข้างต้น มันทำงานได้ดีสำหรับจำนวนคะแนนและบรรทัดที่สมเหตุสมผล แต่ฉันไม่ได้ลองด้วยวัตถุจำนวนมาก

แน่นอนว่าจะต้องมีการคัดลอกในขณะที่ไม่ได้ใช้งานหรือ "pythonic solution" อื่น ๆ ที่น้อยกว่าและบันทึกเป็น

ในกล่องเครื่องมือ 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

ขึ้นอยู่กับความสนใจและการใช้งานของคุณมันอาจจะมีประโยชน์ในการดู "อัลกอริทึมการจับคู่แผนที่" ตัวอย่างเช่นมีเป็นโครงการ RoadMatcher บน OSM วิกิพีเดีย: http://wiki.openstreetmap.org/wiki/Roadmatcher


มันมีไว้สำหรับความต้องการการเดินทางและการคาดการณ์ โดยปกติเราแบ่งพื้นที่ออกเป็นโซนการวิเคราะห์การจราจร (รูปหลายเหลี่ยม) และเราสร้างเซนทรอยด์ของรูปหลายเหลี่ยมเป็นผู้เริ่มต้น "จำลอง" ของการรับส่งข้อมูลทั้งหมดในโซนนั้น จากนั้นเราจะวาดเส้น x หรือ y "ลิงค์จำลองถนนจำลอง" จากจุดนั้นไปยังถนนที่ใกล้ที่สุดและกระจายการจราจรอย่างเท่าเทียมกันจากโซนนั้นไปยังลิงก์จำลองเหล่านั้นและเข้าสู่ชั้นถนนจริง
dassouki

อ้าดังนั้นเป้าหมายของคุณคือสร้างการเชื่อมโยง "จำลองถนน" โดยอัตโนมัติ
underdark

แน่นอน :) หรือลิงก์จำลอง
dassouki
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.