Kliping “serakah” dengan poligon

9

Saya ingin memotong satu set polyline (garis hitam pada gambar di bawah) ke batas luar poligon. Setiap rongga dalam poligon harus diabaikan. Output ideal saya adalah garis kuning putus-putus. Garis awal mungkin lurus atau tidak. Gambar adalah contoh yang disederhanakan, pada kenyataannya poligon jauh lebih kompleks dan ada ratusan garis. Saya tidak berpikir cangkang cembung akan bekerja (tapi saya mungkin salah). Saya terbuka untuk solusi dalam arcgis, qgis, arcpy, rupawan, dll. Pengkodean sebaiknya dalam python oleh saya terbuka untuk opsi lain jika perlu. Arcgis juga akan lebih disukai untuk membuatnya lebih mudah bagi rekan kerja saya untuk berbagi alat tetapi bukan keharusan.

Yang terbaik yang dapat saya pikirkan saat ini adalah memotong garis individual dengan poligon yang menciptakan satu set titik di semua persimpangan batas. Urutkan titik menurut jarak ke awal garis. Poin terjauh dan terdekat (FAC) akan menjadi batas luar poligon. Kemudian gunakan titik FAC untuk memilih simpul yang tepat dari garis asli dan membuat garis putus-putus kuning dari titik yang sesuai. Seharusnya bekerja tetapi tampaknya lebih rumit dari yang diperlukan.

Beberapa pemikiran tambahan:

  • Garis linier "cukup" sehingga perhitungan jarak sederhana antara titik harus bekerja, referensi linear tidak perlu.
  • Ini akan mudah di arcpy jika ada alat untuk membagi garis pada suatu titik tetapi saya tidak dapat menemukannya.

Pikiran siapa pun?

Contoh

Mike Bannister
sumber
+1, masalah menarik! Saya ingin melihat solusi apa yang tersedia =)
Joseph
Hanya garis tengah Anda yang sulit dicapai - bagian atas dan bawah hanya berasal dari klip setelah mengisi lubang. Akibatnya, saya pikir Anda harus memfokuskan pertanyaan Anda pada itu dan mempersempit ruang lingkupnya hanya ArcPy jika itu adalah alat pilihan Anda. Anda selalu dapat bertanya tentang alat lain, jika itu tidak menghasilkan solusi.
PolyGeo
Apakah garis melewati beberapa poligon?
Emil Brundage
Emil, mari kita asumsikan bahwa garis dapat melewati beberapa poligon. Namun, selain geometri tidak ada perbedaan antara poligon sehingga dapat dibubarkan, digabungkan menjadi fitur multi-bagian, dll. Jika itu membuat algoritma lebih mudah. Garis yang melintasi beberapa poligon kemungkinan akan jarang dan itu bisa menjadi kasus yang ditandai untuk ditangani dengan tangan jika perlu.
Mike Bannister
Apa tingkat lisensi Anda?
Emil Brundage

Jawaban:

4

Saya ingin memasukkan solusi pyQGIS saya, tidak ada yang lain.

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)

Kasing pengujian saya - sebelum kliping: sebelum klip

Setelah kliping:

setelah

Untuk mendapatkan set lengkap atribut dari garis asli saya pikir itu akan menjadi yang terbaik untuk bergabung dengan mereka dengan hasilnya. Kalau tidak, harus dibuat di bagian persiapan, dan diatur dalam loop paling dalam. Tetapi saya belum menguji apakah mereka lulus proses pembubaran atau jika mereka tersesat, karena pada prinsipnya mereka dapat memiliki nilai yang berbeda.

Detlev
sumber
Jawaban yang sangat singkat. Bagaimana hasil tangkapan layar QGIS selalu terlihat seperti QGIS?
Mike Bannister
3

Ini akan mudah di arcpy jika ada alat untuk membagi garis pada suatu titik tetapi saya tidak dapat menemukannya.

Jika Anda menjalankan Integrasi dengan poligon dan garis sebagai input, itu akan menambahkan simpul ke masing-masing di mana mereka berpotongan. (Hati-hati, ketika Integrasi memodifikasi input alih-alih menghasilkan output baru.)

Setelah Anda yakin ada simpul yang bertepatan, Anda dapat beralih dari simpul garis dan uji untuk melihat apakah masing-masing menyentuh fitur lainnya. Dari daftar simpul yang diurutkan yang melakukan sentuhan, ambil nilai minimum dan maksimum dari set. Kemudian, buat dua baris dari setiap fitur, A: (mulai, ..., mnt) dan B: (maks, ..., akhir).

Pilihan lain, meskipun saya tidak yakin apakah ArcPy mempertahankan urutan bagian fitur berdasarkan urutan simpul pada objek input, akan menjalankan klip apa adanya. Untuk garis tengah dalam contoh Anda, itu harus menghasilkan fitur multi bagian dengan tiga bagian. Bergantung pada pemesanan, Anda dapat beralih di setiap baris multi-bagian yang diproduksi oleh Clip dan menghapus semua kecuali bagian pertama dan terakhir dari fitur multi-bagian.

John Reiser
sumber
3

Ada tiga masalah yang harus dihadapi dalam kasus ini:

  • Lubang
  • Garis antara poligon
  • Garis akhir

masukkan deskripsi gambar di sini

Lubang

Karena setiap garis di dalam lubang akan dipertahankan, lepaskan lubang dari poligon. Dalam skrip di bawah ini saya melakukannya dengan menggunakan kursor dan geometri.

Garis antara poligon

Garis yang menyentuh dua poligon harus dilepas. Dalam skrip di bawah ini saya melakukannya dengan melakukan join spasial one to many, dengan baris saya sebagai kelas fitur input saya dan poligon saya sebagai kelas fitur join saya. Setiap baris yang dihasilkan dua kali menyentuh dua poligon dan dihapus.

Garis akhir

Untuk menghapus garis yang hanya menyentuh poligon di satu ujung, saya mengonversi garis ke titik akhir. Saya kemudian menggunakan lapisan fitur dan pilihan untuk menentukan titik akhir mana yang floaters. Saya memilih titik akhir yang memotong poligon. Saya kemudian mengalihkan pilihan saya. Ini memilih titik akhir yang tidak memotong poligon. Saya memilih garis yang memotong titik-titik yang dipilih ini dan menghapusnya.

Hasil

masukkan deskripsi gambar di sini

Asumsi

  • Input adalah kelas fitur file geodatabase
  • Lisensi lanjutan ArcGIS tersedia (karena suatu erasedan a feature vertices to points)
  • Saluran yang terhubung dan kontinu adalah fitur tunggal
  • Poligon tidak tumpang tindih
  • Tidak ada multipart poligon

Naskah

Skrip di bawah ini menampilkan kelas fitur dengan nama kelas fitur baris Anda plus _GreedyClip, dalam geodatabase yang sama dengan kelas fitur baris Anda. Lokasi ruang kerja juga dibutuhkan.

#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)  
Emil Brundage
sumber
Solusi yang bagus Emil. Itu kode yang lebih sedikit daripada yang saya dapatkan.
Mike Bannister