¿Cómo acceder de manera eficiente a las funciones devueltas por QgsSpatialIndex?

9

El libro de cocina PyQGIS explica cómo configurar el índice espacial pero solo explica la mitad de su uso:

crear índice espacial: el siguiente código crea un índice vacío

index = QgsSpatialIndex()

agregar características al índice: el índice toma el objeto QgsFeature y lo agrega a la estructura de datos interna. Puede crear el objeto manualmente o usar uno de la llamada anterior a la siguiente Función del proveedor ()

index.insertFeature(feat)

una vez que el índice espacial se llena con algunos valores, puede hacer algunas consultas

# returns array of feature IDs of five nearest features
nearest = index.nearestNeighbor(QgsPoint(25.4, 12.7), 5)

¿Cuál es el paso más eficiente para obtener las características reales que pertenecen a las ID de características devueltas?

bajo oscuro
fuente

Respuestas:

12
    # assume a list of feature ids returned from index and a QgsVectorLayer 'lyr'
    fids = [1, 2, 4]
    request = QgsFeatureRequest()
    request.setFilterFids(fids)

    features = lyr.getFeatures(request)
    # can now iterate and do fun stuff:
    for feature in features:
        print feature.id(), feature

    1 <qgis._core.QgsFeature object at 0x000000000E987510>
    2 <qgis._core.QgsFeature object at 0x000000000E987400>
    4 <qgis._core.QgsFeature object at 0x000000000E987510>
gsherman
fuente
¡Gracias! Snorfalorpagus mencionó que setFilterFids sería considerablemente más lento que la solución que publicó. ¿Confirmas esto?
oscuro
No lo he usado en grandes conjuntos de resultados, así que no puedo confirmar.
gsherman
1
Confirmo y, en mi caso, rtree es incluso más rápido que QgsSpatialIndex () (para la construcción de Gráficos Planar a partir de capas de polilíneas muy grandes, transposición del módulo PlanarGraph con Shapely en PyQGIS. Pero la solución con Fiona, Shapely y rtree sigue siendo la más rápido)
gen
1
Creo que la pregunta se trata de obtener las características reales de los identificadores de características devueltos en lugar de la velocidad de varios métodos de indexación.
gsherman
7

En una publicación de blog sobre el tema , Nathan Woodrow proporciona el siguiente código:

layer = qgis.utils.iface.activeLayer()

# Select all features along with their attributes
allAttrs = layer.pendingAllAttributesList()
layer.select(allAttrs)
# Get all the features to start
allfeatures = {feature.id(): feature for (feature) in layer}

def noindex():
    for feature in allfeatures.values():
        for f in allfeatures.values():
            touches = f.geometry().touches(feature.geometry())
            # It doesn't matter if we don't return anything it's just an example

def withindex():
    # Build the spatial index for faster lookup.
    index = QgsSpatialIndex()
    map(index.insertFeature, allfeatures.values())

    # Loop each feature in the layer again and get only the features that are going to touch.
    for feature in allfeatures.values():
        ids = index.intersects(feature.geometry().boundingBox())
        for id in ids:
            f = allfeatures[id]
            touches = f.geometry().touches(feature.geometry())
            # It doesn't matter if we don't return anything it's just an example

import timeit
print "With Index: %s seconds " % timeit.timeit(withindex,number=1)
print "Without Index: %s seconds " % timeit.timeit(noindex,number=1)

Esto crea un diccionario que le permite buscar rápidamente una QgsFeature usando su FID.

He descubierto que para capas muy grandes esto no es especialmente práctico, ya que requiere mucha memoria. Sin embargo, el uso alternativo (acceso aleatorio de la función deseada) layer.getFeatures(QgsFeatureRequest().setFilterFid(fid))parece ser relativamente lento. No estoy seguro de por qué es así, ya que la llamada equivalente usando los enlaces SWIG OGR layer.GetFeature(fid)parece mucho más rápido que esto.

Snorfalorpagus
fuente
1
El uso de un diccionario fue muy mucho más rápido que layer.getFeatures(QgsFeatureRequest().setFilterFid(fid)). Estaba trabajando en una capa con características de 140k, y el tiempo total para las búsquedas de 140k fue de muchos minutos a segundos.
Håvard Tveite
5

Para las comparaciones, ver más eficiente Unión espacial en Python sin QGIS, ArcGIS, PostGIS, etc . La solución presentada utiliza los módulos Python Fiona , Shapely y rtree (Spatial Index).

Con PyQGIS y el mismo ejemplo dos capas, pointy polygon:

ingrese la descripción de la imagen aquí

1) Sin un índice espacial:

polygons = [feature for feature in polygon.getFeatures()]
points = [feature for feature in point.getFeatures()]
for pt in points: 
    point = pt.geometry()
    for pl  in polygons:
        poly = pl.geometry()
        if poly.contains(point):
            print point.asPoint(), poly.asPolygon()
(184127,122472) [[(183372,123361), (184078,123130), (184516,122631),   (184516,122265), (183676,122144), (183067,122570), (183128,123105), (183372,123361)]]
(183457,122850) [[(183372,123361), (184078,123130), (184516,122631), (184516,122265), (183676,122144), (183067,122570), (183128,123105), (183372,123361)]]
(184723,124043) [[(184200,124737), (185368,124372), (185466,124055), (185515,123714), (184955,123580), (184675,123471), (184139,123787), (184200,124737)]]
(182179,124067) [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]

2) Con el índice espacial R-Tree PyQGIS:

# build the spatial index with all the polygons and not only a bounding box
index = QgsSpatialIndex()
for poly in polygons:
     index.insertFeature(poly)

# intersections with the index 
# indices of the index for the intersections
for pt in points:
    point = pt.geometry()
    for id in index.intersects(point.boundingBox()):
    print id
0
0
1
2

¿Qué significan estos índices?

for i, pt in enumerate(points):
     point = pt.geometry()
     for id in index.intersects(point.boundingBox()):
        print "Point ", i, points[i].geometry().asPoint(), "is in Polygon ", id, polygons[id].geometry().asPolygon()
Point  1 (184127,122472) is in Polygon  0 [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]
Point  2 (183457,122850) is in Polygon  0 [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]
Point  4 (184723,124043) is in Polygon  1 [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]
Point  6 (182179,124067) is in Polygon  2 [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]

Mismas conclusiones que en el Más Eficiente Unión espacial en Python sin QGIS, ArcGIS, PostGIS, etc :

  • Sin un índice, debe recorrer en iteración todas las geometrías (polígonos y puntos).
  • Con un índice espacial delimitador (QgsSpatialIndex ()), itera solo a través de las geometrías que tienen la posibilidad de cruzarse con su geometría actual ('filtro' que puede ahorrar una cantidad considerable de cálculos y tiempo ...).
  • También puede usar otros módulos de Python de índice espacial ( rtree , Pyrtree o Quadtree ) con PyQGIS como en Uso de un índice espacial QGIS para acelerar su código (con QgsSpatialIndex () y rtree )
  • pero un índice espacial no es una varita mágica. Cuando se debe recuperar una gran parte del conjunto de datos, un índice espacial no puede proporcionar ningún beneficio de velocidad.

Otro ejemplo en GIS se: ¿Cómo encontrar la línea más cercana a un punto en QGIS? [duplicar]

gene
fuente
Gracias por toda la explicación adicional. Básicamente, su solución utiliza una lista en lugar de un dict como lo hizo Snorfalorpagus. Así que realmente no parece haber ninguna función layer.getFeatures ([ids]) ...
underdark
El propósito de esta explicación es puramente geométrica y es muy fácil de añadir un layer.getFeatures de función ([IDS]) como en el más eficiente Unión espacial en Python sin QGIS, ArcGIS, PostGIS, etc
gen
0

Aparentemente, el único método para obtener un buen rendimiento es evitar o agrupar llamadas a layer.getFeatures (), incluso si el filtro es tan simple como un fid.

Ahora, aquí está la trampa: llamar a getFeatures es costoso. Si lo llama en una capa vectorial, QGIS deberá configurar una nueva conexión con el almacén de datos (el proveedor de la capa), crear alguna consulta para devolver datos y analizar cada resultado a medida que el proveedor lo devuelve. Esto puede ser lento, especialmente si está trabajando con algún tipo de capa remota, como una tabla PostGIS a través de una conexión VPN.

fuente: http://nyalldawson.net/2016/10/speeding-up-your-pyqgis-scripts/

evod
fuente