Der räumliche RTree-Index führt nicht zu einer schnelleren Schnittberechnung


9

Ich habe Code, mit dem ich feststelle, welche Shapely-Polygone / MultiPolygone sich mit einer Reihe von Shapely-LineStrings schneiden. Durch die Antworten auf diese Frage ist der Code von diesem gegangen:

import fiona
from shapely.geometry import LineString, Polygon, MultiPolygon, shape

# Open each layer
poly_layer = fiona.open('polygon_layer.shp')
line_layer = fiona.open('line_layer.shp')

# Convert to lists of shapely geometries
the_lines = [shape(line['geometry']) for line in line_layer]
the_polygons = [(poly['properties']['GEOID'], shape(poly['geometry'])) for poly in poly_layer]

# Check for Polygons/MultiPolygons that the LineString intersects with
covered_polygons = {}
for poly_id, poly in the_polygons:
    for line in the_lines:
        if poly.intersects(line):
            covered_polygons[poly_id] = covered_polygons.get(poly_id, 0) + 1

wo jede mögliche Kreuzung überprüft wird, dazu:

import fiona
from shapely.geometry import LineString, Polygon, MultiPolygon, shape
import rtree

# Open each layer
poly_layer = fiona.open('polygon_layer.shp')
line_layer = fiona.open('line_layer.shp')

# Convert to lists of shapely geometries
the_lines = [shape(line['geometry']) for line in line_layer]
the_polygons = [(poly['properties']['GEOID'], shape(poly['geometry'])) for poly in poly_layer]

# Create spatial index
spatial_index = rtree.index.Index()
for idx, poly_tuple in enumerate(the_polygons):
    _, poly = poly_tuple
    spatial_index.insert(idx, poly.bounds)

# Check for Polygons/MultiPolygons that the LineString intersects with
covered_polygons = {}
for line in the_lines:
    for idx in list(spatial_index.intersection(line.bounds)):
        if the_polygons[idx][1].intersects(line):
            covered_polygons[idx] = covered_polygons.get(idx, 0) + 1

Dabei wird der räumliche Index verwendet, um die Anzahl der Kreuzungsprüfungen zu verringern.

Mit den Shapefiles, die ich habe (ungefähr 4000 Polygone und 4 Zeilen), führt der ursprüngliche Code 12936 .intersection()Überprüfungen durch und dauert ungefähr 114 Sekunden, um ausgeführt zu werden. Der zweite Code, der den räumlichen Index verwendet, führt nur 1816 .intersection()Überprüfungen durch, die Ausführung dauert jedoch ungefähr 114 Sekunden.

Die Ausführung des Codes zum Erstellen des räumlichen Index dauert nur 1-2 Sekunden, sodass die 1816-Überprüfungen im zweiten Codeteil ungefähr genauso lange dauern wie die 12936-Überprüfungen im ursprünglichen Code (seit dem Laden von Shapefiles und das Konvertieren in Shapely-Geometrien sind in beiden Codeteilen gleich.

Ich kann keinen Grund erkennen, warum der räumliche Index die .intersects()Überprüfung länger dauern würde, daher weiß ich nicht, warum dies geschieht.

Ich kann nur denken, dass ich den RTree-Raumindex falsch verwende. Gedanken?

Antworten:


6

Meine Antwort basiert im Wesentlichen auf einer anderen Antwort von @gene hier:

Effizientere räumliche Verbindung in Python ohne QGIS, ArcGIS, PostGIS usw.

Er schlug dieselbe Lösung mit zwei verschiedenen Methoden mit oder ohne räumlichen Index vor.

Er sagte (richtig):

Was ist der Unterschied ?

  • Ohne den Index müssen Sie alle Geometrien (Polygone und Punkte) durchlaufen.
  • Mit einem begrenzten räumlichen Index ( Spatial Index RTree ) iterieren Sie nur durch die Geometrien, die sich mit Ihrer aktuellen Geometrie überschneiden können ('Filter', der eine beträchtliche Menge an Berechnungen und Zeit sparen kann ...)
  • Ein räumlicher Index ist jedoch kein Zauberstab. Wenn ein sehr großer Teil des Datensatzes abgerufen werden muss, kann ein räumlicher Index keinen Geschwindigkeitsvorteil bieten.

Diese Sätze sind selbsterklärend, aber ich habe es vorgezogen, @gene zu zitieren, anstatt die gleichen Schlussfolgerungen wie meine vorzuschlagen (also gehen alle Credits an seine brillante Arbeit!).

Zum besseren Verständnis des Rtree-Raumindex können Sie einige nützliche Informationen über die folgenden Links abrufen:

Eine weitere großartige Einführung in die Verwendung von räumlichen Indizes könnte dieser Artikel von @Nathan Woodrow sein .


Ich verstehe, dass der räumliche Index am besten funktioniert, wenn er die interessierenden Geometrien auf so wenige wie möglich reduzieren kann. Deshalb habe ich die Anzahl der interessierenden Geometrien bei Verwendung der naiven Methode (12936) mit der Anzahl der Geometrien bei Verwendung des räumlichen Index (1816) verglichen. Die intersects()Methode dauert länger, wenn der räumliche Index verwendet wird (siehe Zeitvergleich oben), weshalb ich nicht sicher bin, ob ich den räumlichen Index falsch verwende. Nach dem Lesen der Dokumentation und der verlinkten Beiträge denke ich, dass ich es bin, aber ich hatte gehofft, jemand könnte darauf hinweisen, wenn ich es nicht wäre.
derNincompoop

Ihre letzte Aussage lautete: "Ich kann nur denken, dass ich den Rtree-Raumindex falsch verwende." Thema). Sie versuchen, eine statistische Analyse durchzuführen, aber die Anzahl der Geometrien und Versuche sollte nicht ausreichen, um das Problem besser zu verstehen. Dieses Verhalten kann von der Anzahl der beteiligten Geometrien (eine sehr kleine Anzahl zum Erkennen der Leistung des räumlichen Index) oder von Ihrer Maschine abhängen.
Mgri

4

Nur um die Antwort von mgri zu ergänzen.

Es ist wichtig zu verstehen, was ein räumlicher Index ist ( wie man eine Begrenzungsbox für Shapely & Fiona richtig implementiert? ). Mit meinem Beispiel in Wie man effizient bestimmt, welche von Tausenden von Polygonen sich mit einem Linienstreifen schneiden

Geben Sie hier die Bildbeschreibung ein

Mit den Polygonen können Sie einen räumlichen Index erstellen

idx = index.Index()
for feat in poly_layer:
    geom = shape(feat['geometry'])
    id = int(feat['id'])
    idx.insert(id, geom.bounds,feat)

Grenzen des räumlichen Index (Polygongrenzen in grün)

Geben Sie hier die Bildbeschreibung ein

Oder mit den LineStrings

  idx = index.Index()
  for feat in line_layer:
      geom = shape(feat['geometry'])
      id = int(feat['id'])
      idx.insert(id, geom.bounds,feat)

Grenzen des räumlichen Index (LineString rot gebunden)

Geben Sie hier die Bildbeschreibung ein

Jetzt durchlaufen Sie nur die Geometrien, die sich mit Ihrer aktuellen Geometrie überschneiden können (in Gelb).

Geben Sie hier die Bildbeschreibung ein

Ich verwende hier den räumlichen LineStrings-Index (die Ergebnisse sind dieselben, aber mit Ihrem Beispiel von 4000 Polygonen und 4 Linien ....).

for feat1 in poly_layer:
    geom1 = shape(feat1['geometry'])
    for id in idx.intersection(geom1.bounds):
        feat2 = line_layer[id]
        geom2 = shape(feat2['geometry'])
        if geom2.intersects(geom1):
            print 'Polygon {} intersects line {}'.format(feat1['id'], feat2['id'])

  Polygon 2 intersects line 0
  Polygon 3 intersects line 0
  Polygon 6 intersects line 0
  Polygon 9 intersects line 0

Sie können auch einen Generator verwenden ( example.py )

def build_ind():
     with fiona.open('polygon_layer.shp') as shapes:
         for s in shapes:
             geom = shape(s['geometry'])
             id = int(s['id'])
             yield (id, geom.bounds, s)

 p= index.Property()
 tree = index.Index(build_ind(), properties=p)
 # first line of line_layer
 first = shape(line_layer.next()['geometry'])
 # intersection of first with polygons
 tuple(tree.intersection(first.bounds))
 (6, 2, 3, 9)

Sie können das GeoPandas-Skript sjoin.py untersuchen , um die Verwendung von Rtree zu verstehen .

Es gibt viele Lösungen, aber vergessen Sie das nicht

  • Spatial Index ist kein Zauberstab ...

Wenn ich die naive Methode verwende (bei der ich einen Schnittpunkttest zwischen jeder Polygon- und LineString-Kombination durchführe), führe ich am Ende 12936 solcher Tests durch. Wenn ich den räumlichen Index verwende, muss ich nur 1816 Tests durchführen. Ich glaube, dies bedeutet, dass der räumliche Index in diesem Anwendungsfall einen Wert liefert. Wenn ich jedoch den Code zeitlich festlege, dauert die Durchführung der 1816-Tests genauso lange wie die Durchführung der 12936-Tests. Sollte der Code mit dem räumlichen Index nicht schneller sein, da mehr als 11000 Tests weniger durchgeführt werden?
derNincompoop

Also habe ich mir das angeschaut und festgestellt, dass die ~ 11000 Tests, die nur vom naiven Code ausgeführt werden, weniger als 1 Sekunde dauern, während die 1816-Tests, die von beiden Codesätzen durchgeführt werden, 112 Sekunden dauern. Jetzt verstehe ich, was Sie unter "Raumindex ist kein Zauberstab" verstehen - obwohl die Anzahl der erforderlichen Tests reduziert wurde, waren diejenigen erforderlich, die am meisten zur Zeit beigetragen haben.
derNincompoop

2

Bearbeiten: Um diese Antwort zu verdeutlichen, habe ich fälschlicherweise geglaubt, dass alle Kreuzungstests ungefähr gleich lange gedauert haben. Das ist nicht der Fall. Der Grund, warum ich nicht die Geschwindigkeit erreicht habe, die ich von der Verwendung eines räumlichen Index erwartet hatte, ist, dass die Auswahl der Kreuzungstests diejenigen war, die überhaupt am längsten gedauert haben.

Wie Gen und Mgri bereits gesagt haben, ist ein räumlicher Index kein Zauberstab. Obwohl der räumliche Index die Anzahl der Kreuzungstests, die von 12936 auf nur 1816 durchgeführt werden mussten, reduzierte, waren die Tests von 1816 der Test, dessen Berechnung die meiste Zeit in Anspruch nahm.

Der räumliche Index wird korrekt verwendet, aber die Annahme, dass jeder Schnittpunkttest ungefähr die gleiche Zeit benötigt, ist falsch. Die für den Kreuzungstest erforderliche Zeit kann stark variieren (0,05 Sekunden gegenüber 0,000007 Sekunden).


1
Sie können nicht berücksichtigen, wie der räumliche Index die Geschwindigkeit der weiteren Kreuzung beeinflusst, da er nur zur Komplexität der beteiligten Geometrien gehört. In Ihrem Fall schneiden sich die Geometrien "A" und "B" in 0,05 Sekunden, auch wenn Sie zuvor einen räumlichen Index verwendet haben (dies ist offensichtlich eine theoretische Aussage, da ich denke, dass Sie wissen, dass die Verarbeitung von Alles innerhalb eines Prozessors hängt mit vielen anderen Faktoren zusammen!).
Mgri
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.