Executando uma consulta espacial em um loop no PyQGIS

9

O que estou tentando fazer: laço através de um shapefile ponto e selecione cada ponto que cai em um polígono.

O código a seguir é inspirado em um exemplo de consulta espacial que encontrei em um livro:

mitte_path = r"D:\PythonTesting\SelectByLocation\mitte.shp"
punkte_path = r"D:\PythonTesting\SelectByLocation\punkte.shp"

polygon = QgsVectorLayer(mitte_path, 'Mitte', 'ogr')
points = QgsVectorLayer(punkte_path, 'Berlin Punkte', 'ogr')

QgsMapLayerRegistry.instance().addMapLayer(polygon)
QgsMapLayerRegistry.instance().addMapLayer(points)

polyFeatures = polygon.getFeatures()

pointsCount = 0

for poly_feat in polyFeatures:
    polyGeom = poly_feat.geometry()
    pointFeatures = points.getFeatures(QgsFeatureRequest().setFilterRect(polyGeom.boundingBox()))
    for point_feat in pointFeatures:
        points.select(point_feat.id())
        pointsCount += 1

print 'Total:',pointsCount

Isso funciona e seleciona conjuntos de dados, mas o problema é que ele seleciona por caixa delimitadora , portanto, obviamente, retornando pontos nos quais não estou interessado:

insira a descrição da imagem aqui

Como eu poderia retornar apenas pontos dentro do polígono sem usar qgis: selectbylocation ?

Eu tentei usar os métodos inside () e intersects () , mas como não estava conseguindo que eles funcionassem, recorri ao código acima. Mas talvez eles sejam a chave, afinal.

BritishSteel
fonte

Respostas:

10

Você não precisa de uma função especial (como "Ray Casting"), tudo está no PyQGIS ( contains () no PyQGIS Geometry Handling )

polygons = [feature for feature in polygons.getFeatures()]
points = [feature for feature in points.getFeatures()]
for pt in points: 
     point = pt.geometry() # only and not pt.geometry().asPolygon() 
     for pol in polygons:
        poly = pol.geometry()
        if poly.contains(point):
             print "ok" 

ou em uma linha

 polygons = [feature for feature in polygons.getFeatures()]
 points = [feature for feature in points.getFeatures()]
 resulting = [pt for pt in points for poly in polygons if poly.geometry().contains(pt.geometry())]
 print len(resulting)
 ...

Você também pode usar diretamente

[pt.geometry().asPoint() for pt in points for poly in polygons if poly.geometry().contains(pt.geometry())]

O problema aqui é que você deve percorrer todas as geometrias (polígonos e pontos). É mais interessante usar um índice espacial delimitador: você itera apenas pelas geometrias que têm a chance de se cruzarem com a geometria atual ('filtro', veja Como acessar com eficiência os recursos retornados pelo QgsSpatialIndex? )

gene
fonte
11
Veja também nathanw.net/2013/01/04/…
Nathan W
5

Você pode usar o algoritmo "Ray Casting" que adaptei levemente para usar com o PyQGIS:

def point_in_poly(point,poly):
    x = point.x()
    y = point.y()

    n = len(poly)
    inside = False

    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside

## Test
mapcanvas = iface.mapCanvas()

layers = mapcanvas.layers()

#For polygon 
polygon = [feature.geometry().asPolygon() 
            for feature in layers[1].getFeatures()]

points = [feat.geometry().asPoint() 
           for feat in layers[0].getFeatures()]

## Call the function with the points and the polygon
count = [0]*(layers[1].featureCount())

for point in points:
    i = 0
    for feat in polygon:
        if point_in_poly(point, feat[0]) == True:
            count[i] += 1
        i += 1

print count

Aplicado a esta situação:

insira a descrição da imagem aqui

o resultado, no Python Console, foi:

[2, 2]

Funcionou.

Nota de edição:

Código com a proposta mais concisa de gene :

mapcanvas = iface.mapCanvas()

layers = mapcanvas.layers()

count = [0]*(layers[1].featureCount())

polygon = [feature
           for feature in layers[1].getFeatures()]

points = [feature
          for feature in layers[0].getFeatures()]

for point in points:

    i = 0

    geo_point = point.geometry()

    for pol in polygon:
        geo_pol = pol.geometry()

        if geo_pol.contains(geo_point):
            count[i] += 1
        i += 1

print count
xunilk
fonte
Ótima referência e ótima resposta! Marcarei o que acabei de publicar, no entanto, como a solução, pois é um pouco mais fácil de implementar. Você deve ser recompensado com muitos votos positivos. +1 de mim, com certeza.
precisa
Você não precisa especificar if geo_pol.contains(geo_point) == True:porque está implícito na if geo_pol.contains(geo_point)(sempre True)
gene
3

Com alguns conselhos de um colega de trabalho, finalmente consegui usá-lo usando within ().

Lógica geral

  1. obter recursos do (s) polígono (s)
  2. obter recursos de pontos
  3. percorrer cada recurso do arquivo de polígono e para cada:
    • obter geometria
    • percorrer todos os pontos
      • obter geometria de ponto único
      • testar se a geometria está dentro da geometria do polígono

Aqui está o código:

mitte_path = r"D:\PythonTesting\SelectByLocation\mitte.shp"
punkte_path = r"D:\PythonTesting\SelectByLocation\punkte.shp"

poly = QgsVectorLayer(mitte_path, 'Mitte', 'ogr')
points = QgsVectorLayer(punkte_path, 'Berlin Punkte', 'ogr')

QgsMapLayerRegistry.instance().addMapLayer(poly)
QgsMapLayerRegistry.instance().addMapLayer(points)

polyFeatures = poly.getFeatures()
pointFeatures = points.getFeatures()

pointCounter = 0

for polyfeat in polyFeatures:
    polyGeom = polyfeat.geometry()
    for pointFeat in pointFeatures:
        pointGeom = pointFeat.geometry()
        if pointGeom.within(polyGeom):
            pointCounter += 1
            points.select(pointFeat.id())

print 'Total',pointCounter

Isso também funcionaria com interseções () em vez de dentro de () . Ao usar pontos, não importa qual deles você usaria, pois ambos retornarão o mesmo resultado. Ao verificar linhas / polígonos, no entanto, isso pode fazer uma diferença importante: inside () retorna objetos que estão inteiramente dentro, enquanto intersects () retoca objetos que estão inteiramente dentro e que estão parcialmente dentro (ou seja, que se cruzam com o recurso, como o nome indica).

insira a descrição da imagem aqui

BritishSteel
fonte
Eu tentei sua solução. Ele só funciona se você tiver um polígonos, caso contrário, apenas os pontos dentro do primeiro polígono será selecionado
ilFonta