Comment accéder efficacement aux fonctionnalités renvoyées par QgsSpatialIndex?

9

Le livre de recettes PyQGIS explique comment configurer l'index spatial mais il explique seulement la moitié de son utilisation:

créer un index spatial - le code suivant crée un index vide

index = QgsSpatialIndex()

ajouter des fonctionnalités à l'index - l'index prend l'objet QgsFeature et l'ajoute à la structure de données interne. Vous pouvez créer l'objet manuellement ou en utiliser un depuis l'appel précédent vers la fonction suivante du fournisseur ()

index.insertFeature(feat)

une fois que l'index spatial est rempli de quelques valeurs, vous pouvez faire quelques requêtes

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

Quelle est l'étape la plus efficace pour obtenir les fonctionnalités réelles appartenant aux ID de fonctionnalité renvoyés?

obscur
la source

Réponses:

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
la source
Merci! Snorfalorpagus a mentionné que setFilterFids serait considérablement plus lent que la solution qu'il avait publiée. Le confirmez-vous?
underdark
Je ne l'ai pas utilisé sur de grands ensembles de résultats, je ne peux donc pas le confirmer.
gsherman
1
Je confirme et, dans mon cas, rtree est encore plus rapide que QgsSpatialIndex () (pour la construction de Graphes Planaires à partir de très grandes couches de polylignes, transposition du module PlanarGraph avec Shapely dans PyQGIS. Mais la solution avec Fiona, Shapely et rtree est toujours la le plus rapide)
gène
1
Je crois que la question concerne l'obtention des fonctionnalités réelles à partir des identifiants de fonctionnalité renvoyés plutôt que la vitesse des différentes méthodes d'indexation.
gsherman
7

Dans un article de blog sur le sujet , Nathan Woodrow fournit le code suivant:

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)

Cela crée un dictionnaire vous permettant de rechercher rapidement un QgsFeature en utilisant son FID.

J'ai trouvé que pour les très grandes couches, ce n'est pas particulièrement pratique car cela nécessite beaucoup de mémoire. Cependant, l'alternative (accès aléatoire à la fonctionnalité souhaitée) utilisant layer.getFeatures(QgsFeatureRequest().setFilterFid(fid))semble être relativement très lente. Je ne sais pas pourquoi, car l'appel équivalent utilisant les liaisons SWIG OGR layer.GetFeature(fid)semble beaucoup plus rapide que cela.

Snorfalorpagus
la source
1
L' utilisation d' un dictionnaire était très bien plus rapide que layer.getFeatures(QgsFeatureRequest().setFilterFid(fid)). Je travaillais sur une couche avec des fonctionnalités 140k, et le temps total pour les recherches 140k est passé de plusieurs minutes à quelques secondes.
Håvard Tveite
5

Pour les comparaisons, regardez spatiale plus efficace se joindre en Python sans QGIS, ArcGIS, PostGIS, etc . La solution présentée utilise les modules Python Fiona , Shapely et rtree (Spatial Index).

Avec PyQGIS et le même exemple deux couches, pointet polygon:

entrez la description de l'image ici

1) Sans indice spatial:

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) Avec l' index spatial 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

Que signifient ces indices?

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

Mêmes conclusions que dans Jointure spatiale plus efficace en Python sans QGIS, ArcGIS, PostGIS, etc .:

  • Sans et index, vous devez parcourir toutes les géométries (polygones et points).
  • Avec un index spatial englobant (QgsSpatialIndex ()), vous itérez uniquement à travers les géométries qui ont une chance de se croiser avec votre géométrie actuelle (`` filtre '' qui peut économiser une quantité considérable de calculs et de temps ...).
  • Vous pouvez également utiliser d' autres index spatial modules Python ( rtree , Pyrtree ou QuadTree ) avec PyQGIS comme dans l' aide d' un index spatial QGIS pour accélérer votre code (avec QgsSpatialIndex () et rtree )
  • mais un indice spatial n'est pas une baguette magique. Lorsqu'une très grande partie de l'ensemble de données doit être récupérée, un index spatial ne peut pas apporter d'avantage de vitesse.

Autre exemple dans SIG se: Comment trouver la ligne la plus proche d'un point dans QGIS? [dupliquer]

gène
la source
Merci pour toutes les explications supplémentaires. Fondamentalement, votre solution utilise une liste au lieu d'un dict comme l'a fait Snorfalorpagus. Donc, il ne semble vraiment pas y avoir de fonction layer.getFeatures ([ids]) ...
underdark
Le but de cette explication est purement géométrique et il est très facile d'ajouter une fonction layer.getFeatures ([ids]) comme dans la jointure spatiale plus efficace en Python sans QGIS, ArcGIS, PostGIS, etc.
gène
0

Apparemment, la seule méthode pour obtenir de bonnes performances est d'éviter ou de regrouper les appels à layer.getFeatures (), même si le filtre est aussi simple qu'un fid.

Maintenant, voici le piège: appeler getFeatures coûte cher. Si vous l'appelez sur une couche vectorielle, QGIS devra configurer une nouvelle connexion au magasin de données (le fournisseur de couches), créer une requête pour renvoyer des données et analyser chaque résultat tel qu'il est renvoyé par le fournisseur. Cela peut être lent, surtout si vous travaillez avec un certain type de couche distante, comme une table PostGIS sur une connexion VPN.

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

evod
la source