"Жадібні" відсікання ліній з багатокутником


9

Я хочу прикріпити набір поліліній (чорні лінії на зображенні внизу) до зовнішньої межі багатокутника. Будь-які порожнечі всередині полігону слід ігнорувати. Мій ідеальний вихід - пунктирні жовті лінії. Початкові лінії можуть бути, а можуть і не бути прямими. Зображення є спрощеним прикладом, насправді багатокутник набагато складніший і є сотні ліній. Я не думаю, що опуклий корпус спрацював (але я можу помилитися). Я відкритий для рішень у arcgis, qgis, arcpy, shapely тощо. Кодування, бажано, буде в python, якщо я необхідний, я відкритий для інших варіантів. Arcgis також був би кращим, щоб полегшити моїм колегам обмін інструментом, але це не є вимогою.

Найкраще, про що я можу подумати зараз, - це перетинати окрему лінію з багатокутником, створюючи набір точок на всіх граничних перетинах. Сортуйте точки за відстанню до початку рядка. Найбільш віддаленою та найближчою (FAC) точками буде зовнішня межа полігону. Потім за допомогою точок FAC виберіть правильні вершини з початкового рядка та створіть жовту пунктирну лінію з відповідних точок. Це має працювати, але здається складнішим, ніж потрібно.

Кілька додаткових думок:

  • Рядки лінійні "достатньо", щоб простий розрахунок відстані між точками повинен працювати, лінійне посилання не повинно бути необхідним.
  • Це було б легко в архпі, якби був інструмент для розділення лінії в точці, але я не можу її знайти.

Думки когось?

Приклад


+1, цікава проблема! Мені цікаво побачити, які рішення доступні =)
Йосип

Тільки середньої лінії важко досягти - верхня і нижня просто виходять із кліпу після заповнення будь-яких порожнеч. Отже, я думаю, ви повинні зосередити своє питання на цьому і звузити його сферу дії лише на ArcPy, якщо це ваш бажаний інструмент. Ви завжди можете запитати про інший інструмент, якщо це не дає рішення.
PolyGeo

чи перетинають лінії багатокутники?
Брюндаж Еміля

Еміль, давайте припустимо, що лінії можуть перетинатися через кілька багатокутників. Однак, крім геометрії, між полігонами немає різниці, тому вони можуть бути розпущені, об'єднані в багаточастинні ознаки тощо, якщо це полегшує алгоритм. Лінія, що перетинає кілька полігонів, швидше за все, буде рідкісною, і це може бути випадком, коли позначений прапором, в разі необхідності розглядатися вручну.
Майк Банністер

Який у вас рівень ліцензії?
Еміля

Відповіді:


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?
Майк Банністер

3

Це було б легко в архпі, якби був інструмент для розділення лінії в точці, але я не можу її знайти.

Якщо запустити Інтеграцію з полігонами та лініями як вхідні дані, вона додасть вершину кожному, де вони перетинаються. (Обережно, оскільки Integrate змінює вхідні дані замість того, щоб виробляти нові результати.)

Як тільки ви впевнені, що збігаються вершини, ви можете перебирати вершини рядка і перевірити, чи стосується кожна інша функція. З упорядкованого списку вершин, які торкаються, візьміть мінімум та максимум із набору. Потім з кожної функції зробіть два рядки: A: (початок, ..., хв) та B: (max, ..., end).

Іншим варіантом, хоча я не впевнений, чи ArcPy зберігає функцію впорядкування частини на основі упорядкування вершин у вхідному об'єкті, - це запустити кліп таким, яким він є. Для середнього рядка у вашому прикладі це має призвести до багаточастинної функції з трьох частин. Залежно від порядку замовлення, ви можете переглядати кожен багаторядковий рядок, створений Clip, та видалити всі, крім першої та останньої частини багатофункціональної функції.


3

У цьому випадку можна вирішити три питання:

  • Отвори
  • Лінії між багатокутниками
  • Кінцеві лінії

введіть тут опис зображення

Отвори

Оскільки будь-яка лінія в отворі буде підтримуватися, видаліть отвори з багатокутників. У нижченаведеному сценарії я роблю це за допомогою курсорів та геометрій.

Лінії між багатокутниками

Лінії, які торкаються двох багатокутників, потрібно видалити. У нижченаведеному сценарії я це роблю, виконуючи просторове з'єднання one to manyз моїми рядками, як мій клас вхідних ознак, і мої полігони як мій клас функції об'єднання. Будь-яка лінія, яка утворюється двічі, торкається двох многокутників і видаляється.

Кінцеві лінії

Щоб видалити лінії, які торкаються лише багатокутника на одному кінці, я перетворюю лінії в кінцеві точки. Потім я використовую функціональні шари та вибір, щоб визначити, які кінцеві точки є плавцями. Я вибираю кінцеві точки, що перетинаються багатокутники. Потім я перемикаю свій вибір. Це вибирає кінцеві точки, які не перетинаються багатокутників. Я вибираю будь-який рядок, який перетинає ці вибрані точки, і видаляю їх.

Результат

введіть тут опис зображення

Припущення

  • Вхідні дані - це файлові класи функцій бази даних геоданих
  • Доступна розширена ліцензія 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)  

Гарне рішення Еміль. Це менше коду, ніж я закінчила.
Майк Банністер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.