多角形の「グリーディ」なクリッピングライン


9

一連のポリライン(下の画像の黒い線)をポリゴンの外側の境界にクリップしたいと思います。ポリゴン内のボイドはすべて無視する必要があります。私の理想的な出力は黄色の破線です。最初の線はまっすぐな場合とそうでない場合があります。画像は単純化された例ですが、実際にはポリゴンははるかに複雑で、数百の線があります。凸包は機能しないと思います(ただし、私は間違っているかもしれません)。私はarcgis、qgis、arcpy、shapelyなどのソリューションを利用できます。必要に応じて、他のオプションを利用できるようにすることで、コーディングをPythonで行うことができます。Arcgisは、同僚がツールを共有しやすくするためにも望ましいですが、必須ではありません。

私が今考えることができる最良のことは、個々の線を多角形と交差させ、すべての境界交差で一連の点を作成することです。線の始点までの距離でポイントを並べ替えます。最も遠くて最も近い(FAC)ポイントは、ポリゴンの外側の境界になります。次に、FACポイントを使用して元の線から適切な頂点を選択し、適切な点から黄色の破線を作成します。動作するはずですが、必要以上に複雑に見えます。

いくつかの追加の考え:

  • ラインは線形であり、ポイント間の単純な距離計算が機能するため、線形参照は必要ありません。
  • これは、あるポイントでラインを分割するツールがあったとしても、それを見つけることができなければ、arcpyで簡単です。

誰でも考えましたか?

例


+1、面白い問題!どのようなソリューションが利用できるかを知りたい=)
ジョセフ

あなたの真ん中の線だけを達成するのは難しいです-上部と下部は、ボイドを埋めた後にクリップから来るだけです。したがって、あなたの質問に焦点を当て、それが好ましいツールである場合は、その範囲をArcPyだけに絞り込む必要があると思います。それでも解決策が得られない場合は、いつでも別のツールについて質問できます。
PolyGeo

線は複数のポリゴンと交差しますか?
Emil Brundage 2016年

エミール、線が複数のポリゴンを横切る可能性があると仮定しましょう。ただし、ジオメトリ以外はポリゴン間に違いがないため、アルゴリズムを簡単にする場合は、ポリゴンを分解したり、マルチパートフィーチャにマージしたりできます。複数のポリゴンにまたがる線はまれである可能性が高く、必要に応じて手動で対処する必要がある場合があります。
Mike Bannister、2016年

あなたのライセンスレベルは?
Emil Brundage

回答:


4

私のpyQGISソリューションを投入したいだけです。

from PyQt4.QtCore import QVariant
from qgis.analysis import QgsGeometryAnalyzer

# get layers
lines = QgsMapLayerRegistry.instance().mapLayersByName('lines')[0]
clipper = QgsMapLayerRegistry.instance().mapLayersByName('clipper')[0]

# prepare result layer
clipped = QgsVectorLayer('LineString?crs=epsg:4326', 'clipped', 'memory')
clipped.startEditing()
clipped.addAttribute(QgsField('fid', QVariant.Int))
fni = clipped.fieldNameIndex('fid')
clipped.commitChanges()

prov = clipped.dataProvider()
fields = prov.fields()

for line in lines.getFeatures():
    # to increase performance filter possible clippers 
    clippers = clipper.getFeatures(QgsFeatureRequest().setFilterRect(line.geometry().boundingBox()))
    for clip in clippers:
            # split the line
            line1 = line.geometry().splitGeometry(clip.geometry().asPolygon()[0], True)
            feats = []
            # get the split points
            vertices = [QgsPoint(vert[0], vert[1]) for vert in line1[2]]
            for part in line1[1]:
                # for each split part check, if first AND last vertex equal to split points
                if part.vertexAt(0) in vertices and part.vertexAt(len(part.asPolyline())-1) in vertices:
                    # if so create feature and set fid to original line's id
                    feat = QgsFeature(fields)
                    feat.setAttributes([line.id()])
                    feat.setGeometry(part)
                    feats.append(feat)

            prov.addFeatures(feats)

# expose layer
clipped.updateExtents()
QgsMapLayerRegistry.instance().addMapLayers([clipped])

# now dissolve lines having the same value in field fni: here original line's id
diss = QgsGeometryAnalyzer()
diss.dissolve(clipped, 'E:\\clipped.shp', uniqueIdField=fni)

私のテストケース-クリッピング前: クリップ前

クリッピング後:

後

元の行の属性の完全なセットを取得するには、それらを結果と結合するのが最善だと思います。それ以外の場合は、準備セクションで作成し、最も内側のループに設定する必要があります。しかし、それらがディゾルブプロセスに合格するかどうか、またはそれらが失われるかどうかは、原則として異なる値を持つ可能性があるため、テストしていません。


非常に簡潔な答え。QGISスクリーンショットは常にQGISのように見えますか?
Mike Bannister

3

これは、あるポイントでラインを分割するツールがあったとしても、それを見つけることができなければ、arcpyで簡単です。

ポリゴンとラインを入力として統合を実行すると、それらが交差するそれぞれに頂点が追加されます。(Integrateは新しい出力を生成する代わりに入力を変更するため、注意が必要です。)

一致する頂点があることが確認できたら、ラインの頂点を反復処理して、それぞれが他のフィーチャに接触しているかどうかをテストできます。接触する頂点の順序付きリストから、セットから最小値と最大値を取得します。次に、各フィーチャから2行、A:(開始、...、最小)およびB:(最大、...、終了)を作成します。

ArcPyが入力オブジェクトの頂点の順序に基づいてフィーチャパーツの順序を保持するかどうかはわかりませんが、別のオプションは、クリップをそのまま実行することです。例の真ん中の線の場合、3つの部分からなるマルチパートフィーチャーになるはずです。順序に応じて、Clipによって生成されたすべてのマルチパートラインを反復処理し、出力マルチパートフィーチャの最初と最後以外のすべてを削除できます。


3

この場合、対処すべき3つの問題があります。

  • ポリゴン間の線
  • エンドライン

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

穴内のラインは維持されるため、ポリゴンから穴を削除します。以下のスクリプトでは、カーソルとジオメトリを使用してこれを行っています。

ポリゴン間の線

2つのポリゴンに接するラインは削除する必要があります。以下のスクリプトでは、の空間結合を実行one to manyして、ラインを入力フィーチャクラスとして、ポリゴンを結合フィーチャクラスとして実行しています。2回生成されたラインは、2つのポリゴンに接触して削除されます。

エンドライン

片方の端でのみポリゴンに接する線を削除するには、線を終点に変換します。次に、フィーチャレイヤーと選択を利用して、フローターであるエンドポイントを特定します。ポリゴンと交差する端点を選択します。次に、選択を切り替えます。これにより、ポリゴンと交差しない端点が選択されます。これらの選択したポイントと交差する線を選択して削除します。

結果

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

仮定

  • 入力はファイルジオデータベースフィーチャクラスです
  • ArcGISの高度なライセンスが利用可能です(eraseおよびによりfeature vertices to points
  • 連続した接続線は単一の機能です
  • ポリゴンは重なりません
  • マルチパートポリゴンはありません

脚本

以下のスクリプトは、ラインフィーチャクラスと_GreedyClip同じジオデータベースに、ラインフィーチャクラスの名前にを加えたフィーチャクラスを出力します。ワークスペースの場所も必要です。

#input polygon feature class
polyFc = r"C:\Users\e1b8\Desktop\E1B8\Workspace\Workspace.gdb\testPolygon2"
#input line feature class
lineFc = r"C:\Users\e1b8\Desktop\E1B8\Workspace\Workspace.gdb\testLine"
#workspace
workspace = r"in_memory"

print "importing"
import arcpy
import os

#generate a unique ArcGIS file name
def UniqueFileName(location = "in_memory", name = "file", extension = ""):
    if extension:
        outName = os.path.join (location, name + "." + extension)
    else:
        outName = os.path.join (location, name)
    i = 0
    while arcpy.Exists (outName):
        i += 1
        if extension:
            outName = os.path.join (location, "{0}_{1}.{2}".format (name, i, extension))
        else:
            outName = os.path.join (location, "{0}_{1}".format (name, i))
    return outName

#remove holes from polygons
def RemoveHoles (inFc, workspace):
    outFc = UniqueFileName (workspace)
    array = arcpy.Array ()
    sr = arcpy.Describe (inFc).spatialReference
    outPath, outName = os.path.split (outFc)
    arcpy.CreateFeatureclass_management (outPath, outName, "POLYGON", spatial_reference = sr)
    with arcpy.da.InsertCursor (outFc, "SHAPE@") as iCurs:
        with arcpy.da.SearchCursor (inFc, "SHAPE@") as sCurs:
            for geom, in sCurs:
                try:
                    part = geom.getPart (0)
                except:
                    continue
                for pnt in part:
                    if not pnt:
                        break
                    array.add (pnt)
                polygon = arcpy.Polygon (array)
                array.removeAll ()
                row = (polygon,)
                iCurs.insertRow (row)
    del iCurs
    del sCurs
    return outFc

#split line fc by polygon fc
def SplitLinesByPolygon (lineFc, polygonFc, workspace):
    #clip
    clipFc = UniqueFileName(workspace)
    arcpy.Clip_analysis (lineFc, polygonFc, clipFc)
    #erase
    eraseFc = UniqueFileName(workspace)
    arcpy.Erase_analysis (lineFc, polygonFc, eraseFc)
    #merge
    mergeFc = UniqueFileName(workspace)
    arcpy.Merge_management ([clipFc, eraseFc], mergeFc)
    #multipart to singlepart
    outFc = UniqueFileName(workspace)
    arcpy.MultipartToSinglepart_management (mergeFc, outFc)
    #delete intermediate data
    for trash in [clipFc, eraseFc, mergeFc]:
        arcpy.Delete_management (trash)
    return outFc

#remove lines between two polygons and end lines
def RemoveLines (inFc, polygonFc, workspace):
    #check if "TARGET_FID" is in fields
    flds = [f.name for f in arcpy.ListFields (inFc)]
    if "TARGET_FID" in flds:
        #delete "TARGET_FID" field
        arcpy.DeleteField_management (inFc, "TARGET_FID")
    #spatial join
    sjFc = UniqueFileName(workspace)
    arcpy.SpatialJoin_analysis (inFc, polygonFc, sjFc, "JOIN_ONE_TO_MANY")
    #list of TARGET_FIDs
    targetFids = [fid for fid, in arcpy.da.SearchCursor (sjFc, "TARGET_FID")]
    #target FIDs with multiple occurances
    deleteFids = [dFid for dFid in targetFids if targetFids.count (dFid) > 1]
    if deleteFids:
        #delete rows with update cursor
        with arcpy.da.UpdateCursor (inFc, "OID@") as cursor:
            for oid, in cursor:
                if oid in deleteFids:
                    cursor.deleteRow ()
        del cursor
    #feature vertices to points
    vertFc = UniqueFileName(workspace)
    arcpy.FeatureVerticesToPoints_management (inFc, vertFc, "BOTH_ENDS")
    #select points intersecting polygons
    arcpy.MakeFeatureLayer_management (vertFc, "vertLyr")
    arcpy.SelectLayerByLocation_management ("vertLyr", "", polygonFc, "1 FEET")
    #switch selection
    arcpy.SelectLayerByAttribute_management ("vertLyr", "SWITCH_SELECTION")
    arcpy.MakeFeatureLayer_management (inFc, "lineLyr")
    #check for selection
    if arcpy.Describe ("vertLyr").FIDSet:
        #select lines by selected points
        arcpy.SelectLayerByLocation_management ("lineLyr", "", "vertLyr", "1 FEET")
        #double check selection (should always have selection)
        if arcpy.Describe ("lineLyr").FIDSet:
            #delete selected rows
            arcpy.DeleteFeatures_management ("lineLyr")

    #delete intermediate data
    for trash in [sjFc, "vertLyr", "lineLyr"]:
        arcpy.Delete_management (trash)

#main script
def main (polyFc, lineFc, workspace):

    #remove holes
    print "removing holes"
    holelessPolyFc = RemoveHoles (polyFc, workspace)

    #split line at polygons
    print "splitting lines at polygons"
    splitFc = SplitLinesByPolygon (lineFc, holelessPolyFc, workspace)

    #delete unwanted lines
    print "removing unwanted lines"
    RemoveLines (splitFc, polyFc, workspace)

    #create output feature class
    outFc = lineFc + "_GreedyClip"
    outFcPath, outFcName = os.path.split (outFc)
    outFc = UniqueFileName (outFcPath, outFcName)
    arcpy.CopyFeatures_management (splitFc, outFc)
    print "created:"
    print outFc
    print
    print "cleaning up"
    #delete intermediate data
    for trash in [holelessPolyFc, splitFc]:
        arcpy.Delete_management (trash)

    print "done"                    

if __name__ == "__main__":
    main (polyFc, lineFc, workspace)  

素敵なソリューションエミール。それは私が最終的にしたよりも少ないコードです。
マイク・バニスター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.