Il vicino più vicino tra il livello punto e il livello linea? [chiuso]


37

Ho fatto questa domanda più volte su StackOverflow e IRC tra #qgis e #postgis e ho anche provato a codificarlo o implementarlo da solo in Postgis senza una vera risposta.

Usando la programmazione (preferibilmente python), vorrei disegnare una linea da un livello punto, alla sua proiezione sulla linea più vicina di una linea o di un livello poligono.

A partire da ora la maggior parte dei miei dati è nella forma ESRI e nei formati postgis; tuttavia, preferirei stare lontano da una soluzione postgis poiché sono principalmente un utente shp + qgis.

Una soluzione ideale sarebbe quella di implementare GDAL / OGR con python o librerie simili

  • Utilizzando le librerie GDAL / OGR da dove devo iniziare? sarebbe possibile fornire un piano di soluzione?
  • Posso usare NetworkX per fare l'analisi del vicino più vicino?
  • Questo è effettivamente possibile?

Se è più semplice, i punti potrebbero connettersi al punto finale del segmento anziché a un punto proiettato


la linea può essere limitata ad essere orthagonal rispetto al segmento di linea?
WolfOdrade,

@wolfOdrade - Nel complesso, non importa.
Dassouki,

Risposte:


33

Questa domanda si è rivelata un po 'più complicata di quanto pensassi di avere ragione. Esistono molte implementazioni della stessa distanza più breve, come la distanza fornita da Shapely (da GEOS). Tuttavia, poche soluzioni forniscono il punto di intersezione stesso, ma solo la distanza.

Il mio primo tentativo ha bufferizzato il punto in base alla distanza tra il punto e il poligono e ha cercato intersezioni, ma gli errori di arrotondamento impediscono che ciò fornisca una risposta esatta.

Ecco una soluzione completa che utilizza Shapely, basata su queste equazioni :

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

Per i posteri, sembra che questa estensione ArcView gestisca questo problema abbastanza bene, peccato che sia su una piattaforma morta scritta in una lingua morta ...


1
Mi chiedo se c'è un modo per indicizzare i punti poligonali per evitare l'enumerazione esplicita ...
mlt

@mlt non è sicuro di cosa stia pensando, ma ci sono alcuni approcci che possono aiutare a seconda della geometria. Potrebbe eseguire alcuni casting di base per determinare i segmenti più vicini rilevanti, se le prestazioni fossero un problema. In tale ottica, spostarlo in C o Pyrex migliorerebbe le cose.
scw,

Voglio dire con pairsesso è algoritmicamente O (n) o qualcosa del genere. La soluzione @eprand forse può essere modificata per usare KNN, ma finora sono riuscito a vivere senza PostGIS ...
mlt

Non posso più modificare il mio commento precedente :( Forse la soluzione di Nicklas Avén con ST_Closestpoint e ST_Shortestline è la più veloce se PostGIS è un'opzione.
mlt

Bene, potresti usare direttamente un algoritmo KNN in Python . Non credo che ST_Shortestline utilizzi KNN, itera anche in base alla mia lettura di postgis.refractions.net/documentation/postgis-doxygen/d1/dbf/…
scw

8

Una risposta PostGIS (per la multistringa, se linestring, rimuovere la funzione 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

Questo è un po 'vecchio, ma stavo cercando soluzioni a questo problema oggi (punto -> linea). La soluzione più semplice che ho incontrato per questo problema correlato è:

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

Se ho capito bene la funzionalità che stai chiedendo è integrata in PostGIS.

Per ottenere un punto proiettato su una linea è possibile utilizzare ST_Closestpoint (su PostGIS 1.5)

Alcuni suggerimenti su come usarlo sono disponibili qui: http://blog.jordogskog.no/2010/02/07/how-to-use-the-new-distance-related-functions-in-postgis-part1/

Ad esempio, è anche possibile trovare il punto più vicino su un poligono rispetto a un altro poligono.

Se si desidera la linea tra i due punti più vicini su entrambe le geometrie, è possibile utilizzare ST_Shortestline. ST_Closestpoint è il primo punto in ST_Shortestline

La lunghezza di ST_Shortestline tra due geometrie è la stessa di ST_Distance tra le geometrie.


3

Vedi il commento qui sotto su come la mia risposta non dovrebbe essere considerata una soluzione affidabile ... Lascerò questo post originale qui solo per consentire ad altri di esaminare il problema.

Se capisco la domanda, questa procedura generale dovrebbe funzionare.

Per trovare il percorso più breve tra un punto (come definito da x, y o x, y, z) e una poliina (come definito da un set di collegamento di x, y o x, y, z) nello spazio euclideo:

1) Da un determinato punto definito dall'utente (lo chiamerò pt0), trova il vertice più vicino di polilinea (pt1). OGRinfo può eseguire il polling dei vertici di una polilinea, quindi i calcoli della distanza possono essere effettuati tramite metodi standard. Ad esempio, scorrere su una distanza calc come: 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) Memorizzare il valore di distanza minima associato (d1) e (pt1)

3) guarda i due segmenti che si allontanano da pt1 (nella stringa lineare di ogrinfo, questi saranno i vertici precedenti e successivi). Registra questi vertici (n2 e n3).

4) crea la formula y = mx + b per ogni segmento

5) Collega il tuo punto (pt0) alla perpendicolare per ognuna di queste due formule

6) Calcola distanza e intersezioni (d2 e d3; pt2, pt3)

7) Confronta le tre distanze (d1, d2, d3) per la più breve. Il pt0 al nodo associato (pt1, pt2 o pt3) è il collegamento più breve.

Questa è una risposta di flusso di coscienza - si spera, la mia immagine mentale del problema e della soluzione si adatta.


Questo non funzionerà in generale. Ad esempio punto = (1,1), linea = ((0,2), (0,3), (3,0), (2,0)). Se lo disegni, puoi vedere i vertici "più vicini" sulla linea non sono adiacenti al segmento che passa più vicino al punto ... Penso che l'unico modo per gestirlo sia controllare ogni segmento (possibilmente usando le finestre di delimitazione per evitare ottimizzalo un po '). HTH.
Tom

3

Ecco uno script Python per QGIS> 2.0 realizzato con i suggerimenti e le soluzioni sopra indicati. Funziona bene per un numero ragionevole di punti e linee. Ma non l'ho provato con un'enorme quantità di oggetti.

Ovviamente doveva essere copiato in idle o quant'altro meno "soluzione pitonica" e salvarlo come "più vicino.punto.py".

Nella casella degli strumenti di QGIS selezionare script, strumenti, aggiungere uno script e sceglierlo.

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

!!! AVVERTIMENTO !!! Fai attenzione che alcuni punti "strani" / errati potrebbero essere prodotti a causa di questo comando di riga:

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

Il counterSelecvalore in esso impostato indica quanti vicini più vicini vengono restituiti. In effetti ogni punto dovrebbe essere proiettato alla distanza più breve possibile da ciascun oggetto linea; e la distanza minima trovata fornirebbe la linea e il punto proiettati corretti come i vicini più vicini che cerchiamo. Per ridurre il tempo di loop, viene utilizzato il comando Neighbor più vicino. La scelta di un counterSelecvalore ridotto a 1 restituirà il "primo" oggetto incontrato (è il riquadro di delimitazione più preciso) e potrebbe non essere quello giusto. Oggetti di dimensioni diverse possono obbligare a scegliere 3 o 5, o anche più oggetti più vicini al fine di determinare la distanza più breve. Più alto è il valore, più tempo ci vuole. Con centinaia di punti e linee inizia a diventare molto lento con 3 o 5 vicini più vicini, con migliaia può bug con tali valori.


3

A seconda dei tuoi interessi e del tuo caso d'uso, potrebbe essere utile esaminare gli "algoritmi di corrispondenza delle mappe". Ad esempio, esiste un progetto RoadMatcher sul wiki di OSM: http://wiki.openstreetmap.org/wiki/Roadmatcher .


È per la domanda e le previsioni di viaggio. Di solito dividiamo le aree in zone di analisi del traffico (poligoni) e stabiliamo il centroide del poligono come il creatore "fittizio" di tutto il traffico in quella zona. Quindi tracciamo le linee x o y "collegamento stradale fittizio" da quel punto alle strade più vicine e distribuiamo il traffico equamente da quella zona su quei collegamenti fittizi e sullo strato stradale effettivo
dassouki,

Ah, quindi il tuo obiettivo è quello di automatizzare la creazione di questo "collegamento stradale fittizio"?
underdark

davvero :) o link fittizi
dassouki,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.