Realizar una consulta espacial en un bucle en PyQGIS

9

Lo que estoy tratando de hacer: bucle a través de un shapefile de puntos y seleccionar cada punto que cae en un polígono.

El siguiente código está inspirado en un ejemplo de consulta espacial que encontré en un libro:

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

Esto funciona y selecciona conjuntos de datos, pero el problema es que selecciona por cuadro delimitador , por lo tanto, obviamente, devuelve puntos que no me interesan:

ingrese la descripción de la imagen aquí

¿Cómo podría hacer que solo devuelva puntos dentro del polígono sin usar qgis: selectbylocation ?

Intenté usar los métodos inside () e intersects () , pero como no conseguía que funcionaran, recurrí al código anterior. Pero tal vez son la clave después de todo.

BritishSteel
fuente

Respuestas:

10

No necesita una función especial (como "Ray Casting"), todo está en PyQGIS ( contiene () en 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" 

o en una línea

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

También puedes usar directamente

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

El problema aquí es que debe recorrer en iteración todas las geometrías (polígonos y puntos). Es más interesante usar un índice espacial delimitador: solo itera a través de las geometrías que tienen la oportunidad de cruzarse con su geometría actual ('filtro', vea ¿Cómo acceder de manera eficiente a las características devueltas por QgsSpatialIndex? )

gene
fuente
1
También vea nathanw.net/2013/01/04/…
Nathan W
5

Puede usar el algoritmo "Ray Casting" que he adaptado ligeramente para usar con 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 situación:

ingrese la descripción de la imagen aquí

El resultado, en la Consola Python, fue:

[2, 2]

Funcionó.

Nota de edición:

Código con la propuesta más concisa del gen :

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
fuente
Gran referencia y gran respuesta! Sin embargo, marcaré el que acabo de publicar como la solución, ya que es un poco más fácil de implementar. Sin embargo, deberías ser recompensado con muchos votos a favor. +1 de mi parte, seguro.
BritishSteel
No es necesario especificarlo if geo_pol.contains(geo_point) == True:porque está implícito en if geo_pol.contains(geo_point)(siempre Verdadero)
gen
3

Con algunos consejos de un compañero de trabajo, finalmente lo puse a trabajar usando dentro de ().

Lógica general

  1. obtener características de polígono (s)
  2. obtener características de puntos
  3. recorrer cada característica del archivo poligonal y para cada una:
    • obtener geometría
    • recorrer todos los puntos
      • obtener geometría de un solo punto
      • probar si la geometría está dentro de la geometría del polígono

Aquí está el 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

Esto también funcionaría con intersects () en lugar de dentro de () . Al usar puntos, no importa cuál use, ya que ambos devolverán el mismo resultado. Sin embargo, al verificar líneas / polígonos, puede hacer una diferencia importante: inside () devuelve objetos que están completamente dentro, mientras que intersects () vuelve a ejecutar objetos que están completamente dentro y que están parcialmente dentro (es decir, que se cruzan con la característica, como el nombre lo indica).

ingrese la descripción de la imagen aquí

BritishSteel
fuente
Probé tu solución. Funciona solo si tiene un polígono, de lo contrario, solo se seleccionarán los puntos dentro del primer polígono
ilFonta