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.

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



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

Ó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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.