Nokta katmanı ve çizgi katmanı arasındaki en yakın komşu? [kapalı]


37

#Qgis ve #postgis arasındaki stackoverflow ve irc ile ilgili birkaç kez bu soruyu sordum ve kodlamayı ya da kendimi postgis'te gerçek bir cevap olmadan kendi kendime uyguladım.

Programlama kullanarak (en çok tercih edilen python), bir nokta katmanından, bir çizgi veya çokgen katmanının en yakın çizgisindeki çıkıntısına bir çizgi çizmek istiyorum.

Şu an itibariyle verilerimin çoğu ESRI'nin biçiminde ve postgis biçiminde; Ancak, ben çoğunlukla bir shp + qgis kullanıcısı olduğum için postgis çözümünden uzak durmayı tercih ederim.

İdeal bir çözüm GDAL / OGR'yi python veya benzeri kütüphanelerle uygulamak olacaktır.

  • GDAL / OGR kütüphanelerini kullanarak nereden başlamalıyım? Bir çözüm planı vermek mümkün müdür?
  • En yakın komşu analizini yapmak için NetworkX'i kullanabilir miyim?
  • Bu gerçekten mümkün mü?

Daha kolaysa, noktalar öngörülen nokta yerine bölüm bitiş noktasına bağlanabilir


Satır, satır segmentine dikey olacak şekilde sınırlandırılabilir mi?
WolfOdrade

@ wolfOdrade - Genel olarak, önemli değil.
dassouki

Yanıtlar:


33

Bu sorunun doğru düşündüğümden biraz daha zor olduğu ortaya çıktı. Şekil verilen mesafe (GEOS'tan) gibi en kısa mesafenin kendisinde birçok uygulama vardır . Çözümlerin çok azı kesişme noktasını kendisi sağlar, ancak yalnızca mesafeyi sağlar.

İlk denemem nokta ile poligon arasındaki mesafeyi tamponladı ve kesişmeler aradı, ancak yuvarlama hataları bunun kesin bir cevap vermesini engelledi.

İşte bu denklemlere dayanarak, Shapely kullanarak komple bir çözüm :

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

Posterite için, ArcView eklentisinin bu problemi oldukça iyi ele alması gibi görünüyor , ölü bir dilde yazılmış ölü bir platformda çok kötü ...


1
Açıkça numaralandırmayı önlemek için poligon noktalarını indekslemenin bir yolu olup olmadığını merak ediyorum ...
mlt 15:03

@ mlt tam olarak ne düşündüğünden emin değil, ancak geometriye bağlı olarak yardımcı olabilecek bazı yaklaşımlar var. Performans bir sorun olsaydı, en yakın ilgili bölümleri belirlemek için bazı temel ışın dökümü yapabilirdi. Bu damarda, bunu C veya Pyrex'e taşımak işleri iyileştirir.
scw

Onunla pairsalgoritmik olarak O (n) ya da başka bir şey. @eprand çözümü KNN kullanmak için belki de değiştirilebilir ancak şu ana kadar PostGIS olmadan yaşamayı başardım ...
mlt

Önceki yorumumu artık düzenleyemiyorum :( PostGIS bir seçenekse Nicklas Avén'in ST_Closestpoint ve ST_Shortestline çözümü en hızlı olanıdır.
mlt

Doğru, doğrudan Python'da bir KNN algoritması kullanabilirsiniz . ST_Shortestline'ın KNN kullandığına inanmıyorum, sadece postgis.refractions.net/documentation/postgis-doxygen/d1/dbf/…
scw

8

Bir PostGIS cevabı (çoklu sıralama için, eğer öyleyse, st_geometryn işlevini kaldırın)

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

Bu biraz eski, ama bugün bu soruna çözüm arıyordum (point -> line). Bu problem için karşılaştığım en basit çözüm:

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

Seni doğru anlarsam, istediğin işlevsellik PostGIS'te yerleşiktir.

Çizgiye yansıtılan bir nokta elde etmek için ST_Closestpoint kullanabilirsiniz (PostGIS 1.5'te)

Nasıl kullanılacağı hakkında bazı ipuçları buradan okuyabilirsiniz: http://blog.jordogskog.no/2010/02/07/how-to-use-the-new-distance-related-functions-in-postgis-part1/

Örneğin bir çokgenin en yakın noktasını başka bir çokgene bulmak da kullanılabilir.

Her iki geometride en yakın iki nokta arasındaki çizgiyi istiyorsanız, ST_Shortestline kullanabilirsiniz. ST_Closestpoint, ST_Shortestline'daki ilk noktadır

İki geometri arasındaki ST_Shortestline uzunluğu, geometriler arasındaki ST_Distance ile aynıdır.


3

Cevabımın güvenilir bir çözüm olarak nasıl değerlendirilmemesi gerektiği ile ilgili aşağıdaki yoruma bakın ... Bu orijinal yazıyı burada bırakıp başkalarının sorunu incelemesini sağlayacağım.

Soruyu anlarsam, bu genel prosedür işe yaramalı.

Öklid boşluğu içindeki bir nokta (x, y veya x, y, z, ile tanımlandığı gibi) ve bir polin (x, y veya x, y, z bağlantı kümesi ile tanımlandığı gibi) arasındaki en kısa yolu bulmak için:

1) Kullanıcı tanımlı bir noktadan (pt0 diyeceğim), polyline'ın en yakın köşesini (pt1) bulun. OGRinfo bir poligonun köşelerini kirletebilir ve daha sonra standart yöntemler ile mesafe hesaplamaları yapılabilir. Örneğin: calc. * Math.cos (ptx_radians) * Math.pow ((Math.sin ((pt0_radians-ptx_radians) / 2)), 2)))

2) İlgili minimum mesafe değerini (d1) ve (pt1) kaydedin.

3) pt1'den uzaklaşan iki bölüme bakın (ogrinfo çizgisinde, bunlar önceki ve sonraki köşeler olacaktır). Bu köşeleri kaydedin (n2 ve n3).

4) her segment için y = mx + b formülü oluşturun

5) Bu iki formülden her biri için puanınızı (pt0) dikey olarak ilişkilendirin

6) Mesafeyi ve kesişme noktalarını (d2 ve d3; pt2, pt3) hesaplayın

7) En kısa olan üç mesafeyi (d1, d2, d3) karşılaştırın. Pt0'ınız ilişkili düğüme (pt1, pt2 veya pt3) en kısa linktir.

Bu bir bilinç akışı cevap - umarım, sorun ve çözüm hakkındaki zihinsel resmim uyuyor.


Bu genel olarak işe yaramaz. Örneğin nokta = (1,1), satır = ((0,2), (0,3), (3,0), (2,0)). Eğer eskiz yaparsanız, çizgideki "en yakın" köşeleri noktaya en yakın olan bölgeye bitişik olmadığını görebilirsiniz ... Bunu ele almanın tek yolunun her kesimi kontrol etmektir. biraz optimize et). HTH.
Tom

3

Yukarıda verilen ipuçlarından ve çözümlerden oluşan QGIS> 2.0 için bir python betiği verilmiştir. Makul bir nokta ve çizgi için iyi çalışıyor. Fakat çok fazla nesne ile denemedim.

Tabii ki boşta veya başka ne olursa olsun "pythonic çözüm" olarak kopyalanmalı ve "closest.point.py" olarak kaydedilmeliydi.

QGIS araç kutusunda komut dosyasına gidin, araçlar, bir komut dosyası ekleyin, onu seçin.

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

!!! UYARI !!! Bu satır komutundan dolayı bazı "tuhaf" / yanlış yansıtılan noktaların üretilebileceğine dikkat edin:

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

İçindeki counterSelecdeğer, en yakın Ne kadar komşunun döndürüleceğini belirler. Aslında, her nokta, her satır nesnesine mümkün olan en kısa mesafede yansıtılmalıdır; ve bulunan minimum mesafe, aradığımız en yakın komşular olarak doğru çizgiyi ve öngörülen noktayı verecektir. Döngü süresini azaltmak için, en yakın Komşu komutu kullanılır. Bir counterSelecdeğerin 1'e düşürülmesini seçmek, bir araya gelen "ilk" nesneyi (tam olarak sınırlayan kutu) döndürür ve doğru olmayabilir. Farklı çizgi boyutu nesneleri seçmek zorunda kalabilir, en kısa mesafeyi belirlemek için 3 veya 5 veya daha yakın nesneler olabilir. Değer ne kadar yüksek olursa, o kadar uzun sürer. Yüzlerce nokta ve çizgiyle, en yakın 3 veya 5 komşu ile çok yavaşlamaya başlar, binlerce kişiyle bu tür değerlere düşebilir.


3

İlgi alanlarınıza ve kullanım durumunuza bağlı olarak, "harita eşleme algoritmaları" na bakmak faydalı olabilir. Örneğin, OSM wiki'sinde bir RoadMatcher projesi var: http://wiki.openstreetmap.org/wiki/Roadmatcher .


Seyahat talebi ve tahmini için. Genellikle alanları trafik analiz bölgelerine (çokgenler) ayırırız ve çokgenin centroidini o bölgedeki tüm trafiğin "sahte" kaynağı olarak oluştururuz. Daha sonra o noktadan en yakın yollara x veya y "sahte yol bağlantısı" çizgileri çizeriz ve trafiği bu bölgeden bu sahte bağlantılar üzerine ve asıl yol katmanına eşit olarak dağıtabiliriz
dassouki

Ah, amacınız bu "sahte yol bağlantısının" oluşturulmasını otomatikleştirmek mi?
underdark

gerçekten :) veya kukla link (ler)
dassouki
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.