具有多边形的“贪婪”剪切线


9

我希望将一组折线(下图中的黑线)剪辑到多边形的外部边界。多边形内的任何空隙均应忽略。我理想的输出是黄色虚线。初始线可能是直线,也可能不是直线。图像是一个简化的示例,实际上多边形要复杂得多,并且有数百条线。我认为凸包不会起作用(但是我可能错了)。我对arcgis,qgis,arcpy,shapely等解决方案持开放态度。最好在python中进行编码,因为如有必要,我也对其他选项开放。为了使我的同事更容易共享工具,Arcgis也将是更可取的,但这不是必需的。

我现在能想到的最好的方法是将一条线与多边形相交,在所有边界相交处创建一组点。按距离到直线起点的距离对点进行排序。最远和最近的(FAC)点将是多边形的外边界。然后,使用FAC点从原始线中选择适当的顶点,并从适当的点创建黄色虚线。它应该可以工作,但看起来比必要的更为复杂。

一些其他想法:

  • 这些线是线性的,足以进行点之间的简单距离计算,而不必进行线性参照。
  • 如果有一种工具可以在一点上分割一条线,但是我找不到它,那么在arcpy中这很容易。

有人在想吗?

例


+1,有趣的问题!我渴望看到可用的解决方案=)
约瑟夫

只有您的中间线很难实现-顶部和底部仅是在填补任何空白后才来自剪辑。因此,我认为您应该将问题重点放在该问题上,并且将其范围缩小到仅ArcPy(如果这是您的首选工具)。您始终可以询问其他工具,如果仍然无法解决。
PolyGeo

线跨多个多边形吗?
Emil Brundage

埃米尔,让我们假设线可以跨越多个多边形。但是,除了几何图形外,多边形之间没有区别,因此可以简化多边形,可以将其分解,合并为多部分要素等。跨越多个多边形的线可能很少见,并且可以作为标记的情况,如果需要的话可以手动处理。
Mike Bannister

您的许可级别是多少?
Emil Brundage

Answers:


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)

我的测试用例-裁剪之前: 剪辑前

裁剪后:

后

为了获得原始行的完整属性集,我认为最好将它们与结果结合在一起。否则,必须在prepare部分中创建,并在最内部的循环中进行设置。但是我尚未测试它们是否通过了溶解过程或是否迷失了,因为原则上它们可以具有不同的值。


答案很简洁。QGIS屏幕快照如何始终看起来像QGIS?
Mike Bannister

3

如果有一种工具可以在一点上分割一条线,但是我找不到它,那么在arcpy中这很容易。

如果以多边形和线作为输入运行“积分”,它将为每个相交的地方添加一个顶点。(小心,因为Integrate修改输入而不是产生新的输出。)

一旦确定存在重合的顶点,就可以遍历直线的顶点并进行测试,以查看它们是否触及其他要素。从确实接触的顶点的有序列表中,选取集合中的最大值和最小值。然后,从每个要素中分两行,分别是A :(开始,...,最小)和B :(最大,...,结束)。

尽管我不确定ArcPy是否会根据输入对象中顶点的顺序保留要素部分的顺序,但另一个选择是按原样运行剪辑。对于示例中的中间线,它应导致包含三部分的多部分功能。根据顺序,您可以遍历Clip产生的每个多部分线,并除去out multipart功能的第一部分和最后部分以外的所有部分。


3

在这种情况下,需要解决三个问题:

  • 孔洞
  • 多边形之间的线
  • 终点线

在此处输入图片说明

孔洞

由于将保留孔内的任何线条,因此请从多边形中移除孔。在下面的脚本中,我通过使用游标和几何来做到这一点。

多边形之间的线

接触两个多边形的线需要删除。在下面的脚本中,我通过执行的空间连接来实现one to many,将线条作为输入要素类,将多边形作为连接要素类。生成两次的任何线都接触两个多边形并被删除。

终点线

为了删除仅一端接触多边形的线,我将线转换为端点。然后,我利用要素图层和选择来确定哪些端点是浮动对象。我选择与多边形相交的端点。然后,我切换选择。这将选择不与多边形相交的端点。我选择与这些选定点相交的任何线并将其删除。

结果

在此处输入图片说明

假设条件

  • 输入是文件地理数据库要素类
  • 可使用ArcGIS Advanced许可证(由于erasefeature vertices to points
  • 连续的连接线是一个功能
  • 多边形不重叠
  • 没有多部分多边形

脚本

下面的脚本在与线要素类_GreedyClip相同的地理数据库中输出具有线要素类名称plus的要素类。还需要一个工作区位置。

#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。这比我最终得到的代码少。
Mike Bannister
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.