RTree空間インデックスの結果、交差計算が速くならない


9

多くのShapely LineStringと交差するShapely Polygon / MultiPolygonを決定するために使用しているコードがあります。この質問への回答を通じて、コードは次のようになっています。

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

ここで、可能なすべての交差がチェックされます:

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

空間インデックスは、交差チェックの数を減らすために使用されます。

私が持っているシェープファイル(約4000ポリゴン、4ライン)では、元のコードは12936 .intersection()チェックを実行し、実行に約114秒かかります。空間インデックスを使用する2番目のコードは1816 .intersection()チェックのみを実行しますが、実行に約114秒もかかります。

空間インデックスを構築するコードの実行には1〜2秒しかかからないため、2番目のコードの1816チェックは、元のコードの12936チェックとほぼ同じ時間で実行されます(シェープファイルとShapelyジオメトリへの変換は、両方のコードで同じです)。

空間インデックスによって.intersects()チェックに時間がかかる理由はわかりません。そのため、これがなぜ起こっているのか途方に暮れています。

RTree空間インデックスを誤って使用しているとしか思えません。考え?

回答:


6

私の答えは基本的にここで@geneによる別の答えに基づいています:

QGIS、ArcGIS、PostGISなどを使用しない、Pythonでのより効率的な空間結合

彼は、空間インデックスの有無にかかわらず、2つの異なる方法を使用して同じソリューションを提案しました。

彼は(正しく)述べた:

違いはなんですか ?

  • インデックスがない場合は、すべてのジオメトリ(ポリゴンとポイント)を反復処理する必要があります。
  • 境界空間インデックス(Spatial Index RTree)を使用すると、現在のジオメトリと交差する可能性のあるジオメトリのみを反復処理します(かなりの量の計算と時間を節約できる「フィルター」...)。
  • しかし、空間インデックスは魔法の杖ではありません。データセットの非常に大きな部分を取得する必要がある場合、空間インデックスは速度の利点を提供できません。

これらの文は自明ですが、私は私と同じ結論を提案するのではなく@geneを引用することをお勧めします(したがって、すべての功績は素晴らしい仕事に使われます!)。

Rtree空間インデックスの理解を深めるために、次のリンクからいくつかの役立つ情報を取得できます。

空間インデックスの使用に関するもう1つの優れた紹介は、@ Nathan Woodrowによるこの記事かもしれません。


空間インデックスは、対象のジオメトリを可能な限り少なくすることができる場合に最適に機能することを理解しています。そのため、単純な方法(12936)を使用する場合の対象となるジオメトリの数を、空間インデックス(1816)を使用する場合のジオメトリの数と比較しました。このintersects()方法は、空間インデックスが使用されている場合(上記の時間比較を参照)に時間がかかるため、空間インデックスを誤って使用しているかどうかがわかりません。ドキュメントとリンクされた投稿を読むことから、私はそう思いますが、私がそうでない場合は誰かが指摘できることを望んでいました。
derNincompoop 2017

あなたの最後の声明は「私はRtree空間インデックスを誤って使用しているとしか思えない」ということだったので、空間インデックスの使用について混乱していると思い、私の回答でその意味を強調しました(オフではありませんでした)トピック)。ある種の統計分析を実行しようとしていますが、関係するジオメトリと試行の数は、問題をよりよく理解するには十分ではありません。この動作は、関係するジオメトリの数(空間インデックスの能力を評価するための非常に小さな数)またはマシンに依存する場合があります。
mgri 2017

4

mgriの回答に追加するだけです。

空間インデックスとは何かを理解することが重要です(Shapely&Fionaのバウンディングボックスを適切に実装する方法)。何千ものポリゴンのうちどれがラインストリングと交差するかを効率的に決定する方法の私の例では

ここに画像の説明を入力してください

ポリゴンで空間インデックスを作成できます

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

空間インデックスの制限(緑のポリゴン境界)

ここに画像の説明を入力してください

またはLineStrings

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

空間インデックスの制限(LineStringは赤でバインド)

ここに画像の説明を入力してください

現在、現在のジオメトリ(黄色)と交差する可能性のあるジオメトリのみを反復処理します。

ここに画像の説明を入力してください

ここではLineStrings空間インデックスを使用します(結果は同じですが、4000ポリゴンと4本のラインの例です...)。

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

ジェネレーター(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)

GeoPandasスクリプトsjoin.pyを調べて、Rtree の使用を理解することができます。

多くの解決策がありますが、それを忘れないでください

  • 空間インデックスは魔法の杖ではありません...

単純な方法(すべてのPolygonとLineStringの組み合わせの間で交差テストを実行する方法)を使用すると、そのようなテストを12936回実行することになります。空間インデックスを使用する場合、1816回のテストを実行するだけで済みます。これは、この使用例で空間インデックスが価値を提供することを意味すると思います。ただし、コードの時間を計ると、1816テストを実行すると、12936テストを実行するのと同じくらい時間がかかります。実行されるテストの数が11000を超えているため、空間インデックスを使用したコードの方が高速ではないでしょうか?
derNincompoop 2017

だから私はこれを調べたところ、単純なコードだけで実行された〜11000テストの実行には1秒未満しかかかりませんでしたが、両方のコードセットで実行された1816テストの実行には112秒かかりました。これで、「空間インデックスは魔法の杖ではない」という意味を理解できました。必要なテストの数を減らしても、必要なテストは、時間に最も貢献したテストでした。
derNincompoop 2017

2

編集:この答えを明確にするために、私はすべての交差テストがほぼ同じ時間かかると間違って信じていました。これはそうではありません。空間インデックスを使用して期待した速度が得られなかった理由は、交差テストの選択が、そもそも最も時間がかかったものだからです。

geneとmgriがすでに述べたように、空間インデックスは魔法の杖ではありません。空間インデックスは、12936から1816にのみ実行する必要がある交差テストの数を削減しましたが、1816テストは、そもそも計算に大部分の時間を費やしたテストです。

空間インデックスは正しく使用されていますが、交差テストごとにほぼ同じ時間がかかるという仮定は正しくありません。交差テストに必要な時間は大きく異なる可能性があります(0.05秒に対して0.000007秒)。


1
空間インデックスは、関連するジオメトリの複雑さのみに属するため、空間インデックスが次の交差の速度にどのように影響するかを考慮することはできません。あなたのケースでは、ジオメトリ「A」と「B」が0.05秒で交差する場合、以前に空間インデックスを使用していても、それらは0.05秒で交差します(これは明らかに理論的なステートメントです。プロセッサ内の何かは他の多くの要因に関連しています!)。
mgri 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.