Vizinho mais próximo entre a camada de pontos e a linha? [fechadas]

37

Eu fiz essa pergunta várias vezes no stackoverflow e no irc entre #qgis e #postgis e também tentei codificá-la ou implementá-la sozinha no postgis sem resposta real.

Usando a programação (mais preferencialmente python), eu gostaria de desenhar uma linha de uma camada pontual até sua projeção na linha mais próxima de uma camada de linha ou polígono.

A partir de agora, a maioria dos meus dados está no formato ESRI e postgis; no entanto, prefiro ficar longe de uma solução postgis, pois sou predominantemente um usuário shp + qgis.

Uma solução ideal seria implementar GDAL / OGR com bibliotecas python ou similares

  • Usando as bibliotecas GDAL / OGR, por onde devo começar? seria possível dar um plano de solução?
  • Posso usar o NetworkX para fazer a análise do vizinho mais próximo?
  • Isso é realmente possível?

Se for mais fácil, os pontos podem se conectar ao ponto final do segmento em vez de um ponto projetado

dassouki
fonte
a linha pode ser restrita a ser ortogonal ao segmento de linha?
WolfOdrade
@wolfOdrade - No geral, isso não importa.
Dassouki 27/07/10

Respostas:

33

Essa questão acabou sendo um pouco mais complicada do que eu pensava. Existem muitas implementações da menor distância propriamente dita, como a distância fornecida Shapely (do GEOS). Poucas das soluções fornecem o próprio ponto de interseção, mas apenas a distância.

Minha primeira tentativa limitou o ponto pela distância entre o ponto e o polígono e procurou por interseções, mas os erros de arredondamento impedem que isso dê uma resposta exata.

Aqui está uma solução completa usando Shapely, com base nessas equações :

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

Para a posteridade, parece que essa extensão do ArcView lida com esse problema muito bem, pena que esteja em uma plataforma morta, escrita em um idioma morto ...

scw
fonte
1
Gostaria de saber se existe uma maneira de pontos de índice de polígonos para evitar enumeração explícita ...
mlt
Não tenho certeza exatamente do que você está pensando, mas existem algumas abordagens que podem ajudar, dependendo da geometria. Poderia fazer algumas transmissões de raios básicas para determinar segmentos mais próximos relevantes, se o desempenho fosse um problema. Nesse sentido, mudar isso para C ou Pyrex melhoraria as coisas.
ACS
Quero dizer com pairsisso é algoritmicamente O (n) ou algo assim. @eprand solução , talvez, pode ser modificado para usar KNN no entanto eu consegui viver sem PostGIS até agora ...
MLT
Eu não posso editar o meu comentário anterior por mais tempo :( Talvez solução de Nicklas Aven com ST_Closestpoint & ST_Shortestline são os mais rápidos se PostGIS é uma opção.
MLT
Certo, você pode usar um algoritmo KNN diretamente no Python . Eu não acredito que ST_Shortestline use KNN, apenas itera também com base na minha leitura de postgis.refractions.net/documentation/postgis-doxygen/d1/dbf/…
scw
8

Uma resposta PostGIS (para cadeia de linhas múltiplas, se cadeia de linhas, remova a função 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
eprand
fonte
4

Isso é um pouco antigo, mas eu estava procurando soluções para esse problema hoje (ponto -> linha). A solução mais simples que me deparei para esse problema relacionado é:

>>> 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)
alphabetasoup
fonte
4

Se eu entendi direito, a funcionalidade solicitada está embutida no PostGIS.

Para obter um ponto projetado em uma linha, você pode usar o ST_Closestpoint (no PostGIS 1.5)

Algumas dicas sobre como usá-lo, você pode ler aqui: http://blog.jordogskog.no/2010/02/07/how-to-use-the-new-distance-related-functions-in-postgis-part1/

Também é útil encontrar o ponto mais próximo de um polígono a outro polígono, por exemplo.

Se você deseja a linha entre os dois pontos mais próximos nas duas geometrias, pode usar ST_Shortestline. ST_Closestpoint é o primeiro ponto em ST_Shortestline

O comprimento de ST_Shortestline entre duas geometrias é o mesmo que ST_Distance entre as geometrias.

Nicklas Avén
fonte
3

Veja o comentário abaixo sobre como minha resposta não deve ser considerada uma solução confiável ... Deixarei este post original aqui apenas para que outras pessoas possam examinar o problema.

Se eu entendi a pergunta, esse procedimento geral deve funcionar.

Para encontrar o caminho mais curto entre um ponto (conforme definido por x, y ou x, y, z) e uma polina (conforme definido por um conjunto de conexões de x, y ou x, y, z) no espaço euclidiano:

1) A partir de um determinado ponto definido pelo usuário (vou chamá-lo de pt0), encontre o vértice mais próximo da polilinha (pt1). O OGRinfo pode pesquisar os vértices de uma polilinha e, em seguida, cálculos de distância podem ser feitos por métodos padrão. Por exemplo, itere em uma distância calculada como: 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) Armazene o valor da distância mínima associada (d1) e (pt1)

3) observe os dois segmentos que se afastam de pt1 (na cadeia de linhas ogrinfo, esses serão os vértices anteriores e subsequentes). Registre esses vértices (n2 e n3).

4) crie a fórmula y = mx + b para cada segmento

5) Relacione seu ponto (pt0) à perpendicular para cada uma dessas duas fórmulas

6) Calcular distância e interseções (d2 e d3; pt2, pt3)

7) Compare as três distâncias (d1, d2, d3) para a menor. Seu pt0 para o nó associado (pt1, pt2 ou pt3) é o link mais curto.

Essa é uma resposta da corrente da consciência - espero que minha imagem mental do problema e da solução se encaixe.

Glennon
fonte
Isso não vai funcionar em geral. Por exemplo, ponto = (1,1), linha = ((0,2), (0,3), (3,0), (2,0)). Se você esboçar isso, poderá ver que os vértices "mais próximos" da linha não são adjacentes ao segmento que passa mais próximo do ponto ... Acho que a única maneira de lidar com isso é verificar todos os segmentos (possivelmente usando caixas delimitadoras para evitar otimizar um pouco). HTH.
Tom
3

Aqui está um script python para QGIS> 2.0, feito a partir das dicas e soluções fornecidas acima. Funciona bem para uma quantidade razoável de pontos e linhas. Mas eu não tentei com uma quantidade enorme de objetos.

É claro que tinha que ser copiado em modo inativo ou qualquer outra "solução pitônica" e salvá-lo como "closest.point.py".

Na caixa de ferramentas QGIS, vá para script, ferramentas, adicione um script e escolha-o.

##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
[email protected]
"""
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()

!!! ATENÇÃO !!! Preste atenção que alguns pontos projetados "estranhos" / incorretos podem ser produzidos devido a este comando de linha:

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

O counterSelecvalor nele define quantos mais próximoNeighbor são retornados. De fato, cada ponto deve ser projetado na menor distância possível para cada objeto de linha; e a distância mínima encontrada daria a linha correta e o ponto projetado como os vizinhos mais próximos que procuramos. Para reduzir o tempo de loop, o comando Vizinho mais próximo é usado. Escolher um counterSelecvalor reduzido para 1 retornará o "primeiro" objeto encontrado (é a caixa delimitadora mais exatamente) e pode não ser o correto. Objetos de tamanho de linha diferentes que podem ser obrigados a escolher podem ser 3 ou 5 ou objetos ainda mais próximos para determinar a menor distância. Quanto maior o valor, mais tempo leva. Com centenas de pontos e linhas, começa a ficar muito lento, com 3 ou 5 vizinhos mais próximos, e com milhares, pode incomodar esses valores.

Jean-Christophe Baudin
fonte
3

Dependendo dos seus interesses e caso de uso, pode ser útil examinar "algoritmos de correspondência de mapa". Por exemplo, há um projeto do RoadMatcher no wiki do OSM: http://wiki.openstreetmap.org/wiki/Roadmatcher .

underdark
fonte
É para demanda e previsão de viagens. Normalmente, dividimos as áreas em zonas de análise de tráfego (polígonos) e estabelecemos o centróide do polígono como o originador "fictício" de todo o tráfego nessa zona. Em seguida, chamar X ou Y linhas de "manequim" link de estrada a partir desse ponto para as estradas mais próximas e distribuir o tráfego igualmente a partir dessa zona para essas ligações manequim e sobre a camada de estrada real
dassouki
Ah, então seu objetivo é automatizar a criação desse "link fictício"?
Underdark
de fato :) ou link (s)
fictício