20万ポリゴンの1次近傍を効率的に見つける


14

208,781国勢調査のブロックグループごとに、すべての1次近隣のFIPS IDを取得したいと思います。すべてのTIGER境界をダウンロードして、1つの1GBシェープファイルにマージしました。

BOUNDARY_TOUCHESのコアでSelectLayerByLocationを使用するArcPythonスクリプトを試しましたが、ブロックグループごとに1秒以上かかります。これは、SelectLayerByLocation検索を同じ状態のグループをブロックするように制限した後でもです。このスクリプトを見つけましたが、内部でSelectLayerByLocationも使用しているため、高速ではありません。

ソリューションはArcベースである必要はありません。Pythonでコーディングするのが最も快適ですが、私は他のパッケージを受け入れています。


2
バージョン9.3以降、これを行うツールが[空間統計]ツールボックスにありました。10.0からは、非常に効率的です。同等のサイズのシェープファイル(1つの状態内のすべてのブロック)で同様の操作を実行したことを思い出し、30分で完了しました。そのうち15回はディスクI / Oのみでした。Pythonソースコードにもアクセスできます。
whuber

Spatial Statisticsのどのジオプロセシングツールを使用しましたか?
dmahr

1
名前を忘れました。これは、ポリゴンの隣接関係のテーブルを作成するためのものです。ヘルプシステムでは、近隣ベースの空間統計ツールを実行する前にこのテーブルを作成することをお勧めします。これにより、ツールが実行されるたびにこの情報をオンザフライで再計算する必要がなくなります。少なくとも9.xバージョンでは、出力が.dbf形式であったという重大な制限がありました。動作しない大規模な入力シェープファイルの場合、その場合、操作を細かく分割するか、Pythonコードをハッキングしてより良い形式で出力する必要があります。
whuber


はい、それだけです。PythonコードはArcGISの内部機能(空間インデックスを使用)を完全に活用し、アルゴリズムを非常に高速にします。
whuber

回答:


3

ArcGIS 10.2 for Desktop、またはそれ以前のバージョンにアクセスできる場合、Polygon Neighbors(Analysis)ツールは次のことを行うと思います。

ポリゴンの連続性(オーバーラップ、一致エッジ、またはノード)に基づく統計情報を含むテーブルを作成します。

ポリゴンの隣人

このタスクを今より簡単にするかもしれません。


ありがとう、PolyGeo。Polygon Neighborsツールがより多くの露出を得るように、受け入れられた回答を更新しました。手動のPythonベースの方法よりも確かに堅牢ですが、大規模なデータセットのスケーラビリティがどのように比較されるかはわかりません。
dmahr

現在10.3を使用していますが、シェープファイルで〜30万の国勢調査ブロックで失敗します。
-soandos

@soandos新しい質問として調査/質問する価値があるかもしれません。
PolyGeo

8

ArcGISを回避するソリューションには、pysalを使用します。次を使用して、シェープファイルからウェイトを直接取得できます。

w = pysal.rook_from_shapefile("../pysal/examples/columbus.shp")

または

w = pysal.queen_from_shapefile("../pysal/examples/columbus.shp")

詳細については、ドキュメントを参照してください。


3

ただ更新。Whuberのアドバイスに従った後、Generate Spatial Weights Matrixは単純にPythonループと辞書を使用して近傍を決定することがわかりました。以下のプロセスを再現しました。

最初の部分は、すべてのブロックグループのすべての頂点をループします。これは、頂点座標をキーとして、その座標に頂点を値として持つブロックグループIDのリストを持つ辞書を作成します。完全な頂点/頂点のオーバーラップのみが隣接関係として登録されるため、これにはトポロジ的に適切なデータセットが必要であることに注意してください。幸い、国勢調査局のTIGERブロックグループシェープファイルはこの点で問題ありません。

2番目の部分は、すべてのブロックグループのすべての頂点をループします。ブロックグループIDをキーとして、そのブロックグループのネイバーIDを値として持つ辞書を作成します。

# Create dictionary of vertex coordinate : [...,IDs,...]
BlockGroupVertexDictionary = {}
BlockGroupCursor = arcpy.SearchCursor(BlockGroups.shp)
BlockGroupDescription = arcpy.Describe(BlockGroups.shp)
BlockGroupShapeFieldName = BlockGroupsDescription.ShapeFieldName
#For every block group...
for BlockGroupItem in BlockGroupCursor :
    BlockGroupID = BlockGroupItem.getValue("BKGPIDFP00")
    BlockGroupFeature = BlockGroupItem.getValue(BlockGroupShapeFieldName)
    for BlockGroupPart in BlockGroupFeature:
        #For every vertex...
        for BlockGroupPoint in BlockGroupPart:
            #If it exists (and isnt empty interior hole signifier)...
            if BlockGroupPoint:
                #Create string version of coordinate
                PointText = str(BlockGroupPoint.X)+str(BlockGroupPoint.Y)
                #If coordinate is already in dictionary, append this BG's ID
                if PointText in BlockGroupVertexDictionary:
                    BlockGroupVertexDictionary[PointText].append(BlockGroupID)
                #If coordinate is not already in dictionary, create new list with this BG's ID
                else:
                    BlockGroupVertexDictionary[PointText] = [BlockGroupID]
del BlockGroupItem
del BlockGroupCursor


#Create dictionary of ID : [...,neighbors,...]
BlockGroupNeighborDictionary = {}
BlockGroupCursor = arcpy.SearchCursor(BlockGroups.shp)
BlockGroupDescription = arcpy.Describe(BlockGroups.shp)
BlockGroupShapeFieldName = BlockGroupDescription.ShapeFieldName
#For every block group
for BlockGroupItem in BlockGroupCursor:
    ListOfBlockGroupNeighbors = []
    BlockGroupID = BlockGroupItem.getValue("BKGPIDFP00")
    BlockGroupFeature = BlockGroupItem.getValue(BlockGroupShapeFieldName)
    for BlockGroupPart in BlockGroupFeature:
        #For every vertex
        for BlockGroupPoint in BlockGroupPart:
            #If it exists (and isnt interior hole signifier)...
            if BlockGroupPoint:
                #Create string version of coordinate
                PointText = str(BlockGroupPoint.X)+str(BlockGroupPoint.Y)
                if PointText in BlockGroupVertexDictionary:
                    #Get list of block groups that have this point as a vertex
                    NeighborIDList = BlockGroupVertexDictionary[PointText]
                    for NeighborID in NeighborIDList:
                        #Don't add if this BG already in list of neighbors
                        if NeighborID in ListOfBGNeighbors:
                            pass
                        #Add to list of neighbors (as long as its not itself)
                        elif NeighborID != BlockGroupID:
                            ListOfBGNeighbors.append(NeighborID)
    #Store list of neighbors in blockgroup object in dictionary
    BlockGroupNeighborDictionary[BlockGroupID] = ListOfBGNeighbors

del BlockGroupItem
del BlockGroupCursor
del BlockGroupVertexDictionary

後から考えてみると、シェープファイルを再度ループする必要のない別の方法を2番目の部分に使用できたことがわかりました。しかし、これは私が使用したものであり、一度に数千のブロックグループでもかなりうまく機能します。私はアメリカ全土でそれをやろうとはしていませんが、州全体で実行できます。


2

代わりに、PostgreSQLとPostGISを使用することもできます。このサイトで同様の計算を実行する方法についていくつか質問をしました。

ソフトウェアのさまざまな部分がどのように組み合わされるかを理解するための急勾配の学習曲線があることがわかりましたが、大きなベクトルレイヤーで計算を行うのにすばらしいことがわかりました。私は何百万ものポリゴンでいくつかの最近傍計算を実行しましたが、ArcGISと比較して迅速でした。


1

いくつかのコメント... esri / ArcGISメソッドは現在、情報を保持するために辞書を使用していますが、コア計算はPolygon Neighbors Toolを使用してC ++で実行されます。このツールは、隣接情報と共有境界の長さなどのオプションの属性を含むテーブルを生成します。情報を繰り返し保存し、その後再利用する場合は、空間ウェイトマトリックスの生成ツールを使用できます。WeightsUtilitiesでこの関数を使用して、隣接情報を含む辞書[ランダムアクセス]を生成することもできます。

contDict = polygonNeighborDict(inputFC, masterField, contiguityType = "ROOK")

inputFC =任意のタイプのポリゴンフィーチャクラス、masterFieldは整数の「一意のID」フィールドであり、{"ROOK"、 "QUEEN"}のcontiguityTypeです。

esriでは、Pythonユーザーの表形式の側面をスキップして、多くのユースケースをはるかに高速化するイテレーターに直行しようとしています。RのPySALとspdepパッケージは素晴らしい選択肢です[ radekの答えを参照]。これらのパッケージのデータ形式としてシェープファイルを使用する必要があると思いますが、これはこのスレッド入力形式と調和しています。重複するポリゴンやポリゴン内のポリゴンをどのように処理するかはわかりません。SWMの生成と、私が説明した関数は、これらの空間リレーションシップを「ROOK」および「QUEEN」ネイバーとしてカウントします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.