Найближчий сусід між точковим шаром та шаром лінії? [зачинено]


37

Я задавав це запитання кілька разів про stackoverflow та irc між #qgis та #postgis, і я також намагався кодувати його чи реалізовувати його у postgis, не маючи реальної відповіді.

Використовуючи програмування (найбільш переважно python), я хотів би намалювати лінію від точкового шару до його проекції на найближчу лінію шару лінії або багатокутника.

На сьогоднішній день більшість моїх даних перебувають у формі ESRI та форматі постгіс; однак я вважаю за краще триматися подалі від постгігічного рішення, оскільки я переважно користувач shp + qgis.

Ідеальним рішенням було б впровадити GDAL / OGR з бібліотеками python або подібними бібліотеками

  • З чого слід починати використання бібліотек GDAL / OGR? чи можна було б дати план рішення?
  • Чи можна використовувати NetworkX для аналізу найближчого сусіда?
  • Чи реально це можливо?

Якщо це простіше, точки можуть підключитися до кінцевої точки сегмента замість прогнозованої точки


чи може обмежуватися лінія ортогональною до відрізка лінії?
WolfOdrade

@wolfOdrade - В цілому це не має значення.
dassouki

Відповіді:


33

Це питання виявилося дещо складнішим, ніж я вважав, що правильно. Існує безліч реалізацій самого короткого відстані, наприклад, надається Shapely відстань (від 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

@ я не впевнений, що саме ти думаєш, але є деякі підходи, які можуть допомогти залежно від геометрії. Можна було б виконати базовий промінь для випромінювання, щоб визначити відповідні найближчі сегменти, якщо продуктивність була проблемою. З цього приводу переміщення цього тексту в C або Pyrex покращило б ситуацію.
scw

Я маю на увазі, pairsщо це алгоритмічно O (n) чи щось. @eprand рішення, можливо, можна змінити, щоб використовувати KNN, але мені вдалося жити без PostGIS поки що ...
mlt

Я не можу більше редагувати свій попередній коментар :( Можливо, рішення Nicklas Avén зі ST_Closestpoint & ST_Shortestline є найшвидшим, якщо PostGIS - це варіант.
mlt

Правильно, ви можете використовувати алгоритм KNN в Python безпосередньо. Я не вірю, що ST_Shortestline використовує KNN, він просто повторюється на основі мого читання postgis.refractions.net/documentation/postgis-doxygen/d1/dbf/…
scw

8

Відповідь PostGIS (для багаторядкових рядків, якщо рядок рядків, видаліть функцію 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_Distance між геометріями.


3

Дивіться коментар нижче щодо того, як моя відповідь не повинна вважатися надійним рішенням ... Я залишу цю оригінальну публікацію тут просто, щоб інші могли вивчити проблему.

Якщо я розумію питання, ця загальна процедура повинна спрацювати.

Щоб знайти найкоротший шлях між точкою (як визначено x, y або x, y, z) і полієном (як визначено сполучним набором 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 рядки це будуть попередні та наступні вершини). Запишіть ці вершини (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.
Том

3

Ось сценарій python для QGIS> 2.0, зроблений із наведених вище підказок та рішень. Це чудово працює за розумну кількість точок і ліній. Але я не пробував цього з величезною кількістю предметів.

Звичайно, це було копіювати в режимі очікування або будь-яким іншим менш "пітонічним рішенням" і зберігати його як "najbliže.point.py".

У вікні інструментів 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 повертаються. Насправді кожна точка повинна проектуватися на найкоротшій відстані до кожного об'єкта рядка; і мінімальна знайдена відстань дала б правильну лінію та прогнозовану точку як найближчих сусідів, яких ми шукаємо. Для скорочення часу циклу використовується найближча команда сусідів. Вибір counterSelecзменшеного значення до 1 поверне "перший" об'єкт, що зустрічається (він точно обмежує поле), і він може бути не правильним. Об'єкти різних розмірів рядків можуть зобов’язати вибрати, може бути 3 або 5, а то й більше найближчих об'єктів, щоб визначити найкоротшу відстань. Чим вище значення, тим більше часу займає. З сотнями точок і ліній він починає дуже повільно з 3 або 5 найближчим сусідом, з тисячами він може помилитися з такими значеннями.


3

Залежно від ваших інтересів та випадку використання, може бути корисним переглянути "алгоритми відповідності карти". Наприклад, є проект RoadMatcher на вікі OSM: http://wiki.openstreetmap.org/wiki/Roadmatcher .


Це для попиту на подорожі та прогнозування. Зазвичай ми ділимо ділянки на зони аналізу трафіку (багатокутники) і встановлюємо центроїд полігону як «манекена», що започаткував увесь трафік у цій зоні. Потім ми проводимо x або y лінії "фіктивного дорожнього зв'язку" від цієї точки до найближчих доріг і розподіляємо рух рівномірно з цієї зони на ці манекенні посилання та на фактичний дорожній шар
dassouki

А, значить, ваша мета автоматизувати створення цього "манекена дорожнього зв'язку"?
underdark

дійсно :) або фіктивне посилання
dassouki
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.