Hàng xóm gần nhất giữa lớp điểm và lớp đường? [đóng cửa]


37

Tôi đã hỏi câu hỏi này nhiều lần trên stackoverflow và irc giữa #qgis và #postgis và tôi cũng đã cố gắng viết mã hoặc tự thực hiện nó trong postgis mà không có câu trả lời thực sự.

Sử dụng lập trình (tốt nhất là python), tôi muốn vẽ một đường từ một lớp điểm, đến hình chiếu của nó trên dòng gần nhất của một đường hoặc lớp đa giác.

Cho đến nay, hầu hết dữ liệu của tôi đều ở định dạng hình dạng và hậu kỳ của ESRI; tuy nhiên, tôi muốn tránh xa một giải pháp hậu kỳ vì tôi chủ yếu là người dùng shp + qgis.

Một giải pháp lý tưởng sẽ là triển khai GDAL / OGR với python hoặc các thư viện tương tự

  • Sử dụng thư viện GDAL / OGR tôi nên bắt đầu từ đâu? nó sẽ có thể đưa ra một kế hoạch giải pháp?
  • Tôi có thể sử dụng NetworkX để phân tích hàng xóm gần nhất không?
  • Điều này thực sự có thể?

Nếu dễ dàng hơn, các điểm có thể kết nối với điểm cuối phân khúc thay vì điểm được chiếu


dòng có thể được giới hạn là trực giao với phân khúc dòng không?
WolfOdrade

@wolfOdrade - Nhìn chung, nó không thành vấn đề.
dassouki

Câu trả lời:


33

Câu hỏi này hóa ra hơi khó hơn tôi nghĩ để làm đúng. Có rất nhiều triển khai của khoảng cách ngắn nhất, chẳng hạn như khoảng cách được cung cấp Shapely (từ GEOS). Tuy nhiên, một số giải pháp cung cấp điểm giao nhau, nhưng chỉ có khoảng cách.

Nỗ lực đầu tiên của tôi đã đệm điểm theo khoảng cách giữa điểm và đa giác, và tìm kiếm các giao điểm, nhưng các lỗi làm tròn ngăn điều này không đưa ra câu trả lời chính xác.

Đây là một giải pháp hoàn chỉnh sử dụng Shapely, dựa trên các phương trình sau:

#!/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)

Đối với hậu thế, có vẻ như tiện ích mở rộng ArcView này xử lý vấn đề này khá độc đáo, quá tệ trên nền tảng chết được viết bằng ngôn ngữ chết ...


1
Tôi tự hỏi liệu có cách nào để lập chỉ mục các điểm đa giác để tránh liệt kê rõ ràng không ...
mlt

@mlt không chắc chắn chính xác những gì bạn nghĩ, nhưng có một số cách tiếp cận có thể giúp tùy thuộc vào hình học. Có thể thực hiện một số phương pháp đúc tia cơ bản để xác định các phân đoạn gần nhất có liên quan, nếu hiệu suất là một vấn đề. Trong đó, chuyển cái này sang C hoặc Pyrex sẽ cải thiện mọi thứ.
scw

Ý tôi là với pairsnó là thuật toán O (n) hoặc một cái gì đó. Giải pháp @eprand có lẽ có thể được sửa đổi để sử dụng KNN tuy nhiên tôi đã xoay sở để sống mà không cần PostGIS cho đến nay ...
mlt

Tôi không thể chỉnh sửa nhận xét trước đó của mình nữa :( Có lẽ giải pháp của Nicklas Avén với ST_Closestpoint & ST_Shortestline là nhanh nhất nếu PostGIS là một tùy chọn.
mlt

Phải, bạn có thể sử dụng thuật toán KNN trong Python trực tiếp. Tôi không tin rằng ST_Shortestline sử dụng KNN, nó chỉ lặp đi lặp lại cũng dựa trên việc tôi đọc postgis.refraction.net/documentation/postgis-doxygen/d1/dbf/,
scw

8

Câu trả lời của PostGIS (đối với đa tuyến, nếu linestring, hãy loại bỏ hàm 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

Điều này hơi cũ, nhưng tôi đã tìm kiếm giải pháp cho vấn đề này ngày hôm nay (điểm -> dòng). Giải pháp đơn giản nhất mà tôi gặp phải cho vấn đề liên quan này là:

>>> 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

Nếu tôi hiểu bạn đúng chức năng bạn yêu cầu được xây dựng trong PostGIS.

Để có được một điểm được chiếu trên một dòng, bạn có thể sử dụng ST_Closestpoint (trên PostGIS 1.5)

Một số gợi ý về cách sử dụng bạn có thể đọc tại đây: http://blog.jordogskog.no/2010/02/07/how-to-use-the-new-distance-related-fifts-in-postgis-part1/

Cũng có thể tìm thấy điểm gần nhất trên một đa giác với một đa giác khác chẳng hạn.

Nếu bạn muốn đường giữa hai điểm gần nhất trên cả hai hình học, bạn có thể sử dụng ST_Shortestline. ST_Closestpoint là điểm đầu tiên trong ST_Shortestline

Độ dài của ST_Shortestline giữa hai hình học giống như ST_Distance giữa các hình học.


3

Xem bình luận bên dưới liên quan đến cách câu trả lời của tôi không nên được coi là một giải pháp đáng tin cậy ... Tôi sẽ để lại bài viết gốc này ở đây để người khác có thể kiểm tra vấn đề.

Nếu tôi hiểu câu hỏi, thủ tục chung này sẽ hoạt động.

Để tìm đường đi ngắn nhất giữa một điểm (như được xác định bởi x, y hoặc x, y, z) và polyine (như được xác định bởi một tập hợp kết nối của x, y hoặc x, y, z) trong không gian Euclide:

1) Từ một điểm do người dùng xác định (tôi sẽ gọi nó là pt0), tìm đỉnh gần nhất của polyline (pt1). OGRinfo có thể thăm dò các đỉnh của một đa tuyến, và sau đó tính toán khoảng cách có thể được thực hiện thông qua các phương pháp tiêu chuẩn. Ví dụ: lặp qua một khoảng cách calc như: 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) Lưu trữ giá trị khoảng cách tối thiểu liên quan (d1) và (pt1)

3) nhìn vào hai phân đoạn xuất phát từ pt1 (trong linestring ogrinfo, đây sẽ là các đỉnh trước và sau). Ghi lại các đỉnh này (n2 và n3).

4) tạo công thức y = mx + b cho mỗi phân đoạn

5) Liên kết điểm của bạn (pt0) với đường vuông góc cho mỗi hai công thức đó

6) Tính khoảng cách và giao điểm (d2 và d3; pt2, pt3)

7) So sánh ba khoảng cách (d1, d2, d3) ngắn nhất. Pt0 của bạn đến nút liên kết (pt1, pt2 hoặc pt3) là liên kết ngắn nhất.

Đó là một dòng câu trả lời ý thức - hy vọng, bức tranh tinh thần của tôi về vấn đề và giải pháp phù hợp.


Điều này sẽ không làm việc nói chung. Ví dụ: điểm = (1,1), dòng = ((0,2), (0,3), (3,0), (2,0)). Nếu bạn phác họa điều đó, bạn có thể thấy các đỉnh "gần nhất" trên đường thẳng không liền kề với đoạn đi gần điểm nhất ... Tôi nghĩ cách duy nhất để xử lý việc này là kiểm tra mọi phân đoạn (có thể sử dụng các hộp giới hạn để tránh tối ưu hóa nó một chút). HTH.
Tom

3

Dưới đây là tập lệnh python cho QGIS> 2.0 được tạo từ các gợi ý và giải pháp được đưa ra ở trên. Nó hoạt động tốt cho một số điểm và đường hợp lý. Nhưng tôi đã không thử nó với số lượng lớn các đối tượng.

Tất nhiên nó phải được sao chép ở chế độ rảnh hoặc bất kỳ "giải pháp pythonic" nào khác và lưu nó dưới dạng "near.point.py".

Trong hộp công cụ QGIS đi tìm tập lệnh, công cụ, thêm tập lệnh, chọn tập lệnh.

##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()

!!! CẢNH BÁO !!! Hãy chú ý rằng một số điểm "lạ" / dự kiến ​​sai có thể được tạo ra do lệnh dòng này:

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

Các counterSelecgiá trị trong đó thiết lập bao nhiêu nearestNeighbor được trả về. Trong thực tế, mỗi điểm nên được chiếu ở khoảng cách ngắn nhất có thể tới từng đối tượng đường; và khoảng cách tối thiểu được tìm thấy sẽ đưa ra đường chính xác và điểm được chiếu như là hàng xóm gần nhất mà chúng ta tìm kiếm. Để giảm thời gian lặp, lệnh Neighbor gần nhất được sử dụng. Chọn counterSelecgiá trị giảm xuống 1 sẽ trả về đối tượng "đầu tiên" đã gặp (hộp giới hạn chính xác hơn) và nó có thể không phải là đối tượng phù hợp. Các đối tượng kích thước dòng khác nhau có thể bắt buộc phải chọn có thể là 3 hoặc 5 hoặc thậm chí nhiều đối tượng gần nhất để xác định khoảng cách ngắn nhất. Giá trị càng cao thì càng mất nhiều thời gian. Với hàng trăm điểm và dòng, nó bắt đầu trở nên rất chậm với 3 hoặc 5 hàng xóm gần nhất, với hàng ngàn điểm có thể có lỗi với các giá trị như vậy.


3

Tùy thuộc vào sở thích và trường hợp sử dụng của bạn, có thể hữu ích khi xem xét "thuật toán khớp bản đồ". Ví dụ: có một dự án RoadMatcher trên wiki OSM: http://wiki.openstreetmap.org/wiki/Roadmatcher .


Đó là cho nhu cầu du lịch và dự báo. Thông thường chúng tôi chia các khu vực thành các khu vực phân tích giao thông (đa giác) và chúng tôi thiết lập tâm của đa giác là người khởi tạo "giả" của tất cả lưu lượng truy cập trong khu vực đó. Sau đó, chúng tôi vẽ các đường x hoặc y "đường nối giả" từ điểm đó đến các đường gần nhất và phân phối lưu lượng bằng nhau từ vùng đó lên các liên kết giả đó và trên lớp đường thực tế
dassouki

Ah, vậy mục tiêu của bạn là tự động hóa việc tạo ra "liên kết đường giả" này?
underdark

thực sự :) hoặc liên kết giả (s)
dassouki
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.