将重叠爆炸到新的非重叠多边形?


10

给定多个以多种方式重叠的多边形,我想从这些要素中迭代导出所有不与其他多边形重叠的多边形。

该产品将具有许多不重叠的功能,将它们加在一起便构成了原始功能。

然后可以将乘积用作区域统计的输入,这比在每个多边形上迭代区域统计要快得多。

我一直试图在ArcPy中对此进行编码,但没有成功。

是否已经存在执行此操作的代码?


您是说要将数据“拼合”为拓扑正确的集合吗?
nagytech

@Geoist ZonalStats需要不重叠的多边形。当您有一个重叠的集合时,一个显而易见但效率低下的解决方案是遍历多边形并一一计算区域统计。选择不重叠的多边形的子集,将zonalstats应用于它们,然后进行迭代将更加有效。问题询问如何有效地进行此类选择。
ub

笨拙的-我认为@Geoist建议根据输入多边形的交集创建一组不重叠的多边形。看这张图片 -(无法在评论中发布图片吗?)。输入在左侧。整个区域被三个多边形覆盖,每个多边形都与其他两个多边形相交。唯一不重叠的子集是单例,这些子集不满足Gotanuki要求并集填充空间的要求。我认为Geoist建议在右侧创建一组适用于zonalstats的非相交区域
Llaves 2012年

我认为最终产品应该是什么有些困惑。你能举个例子吗?我的解释是,您希望输出是不重叠的多边形的选择-同时丢弃或溶解其余的多边形。您正在使用一个或多个要素类吗?
亚伦

1
在我看来,@ gotanuki想要创建最少数量的要素类,这些要素类仅包含来自具有重叠面的面要素类中的非重叠面
PolyGeo

Answers:


14

这是图形着色问题

回想一下,图着色是将颜色分配给图的顶点的方式,使得没有两个共享边的顶点也将具有相同的颜色。具体而言,图的(抽象)顶点是多边形。每当两个顶点相交(作为多边形)时,两个顶点就用(无向)边连接。如果我们对问题采取任何解决方案-这是一系列(例如k)个不相交的多边形集合-并为序列中的每个集合分配唯一的颜色,那么我们将获得一个 k色。希望找到一个小的k

这个问题很难解决,对于任意图形来说仍然无法解决。考虑一个易于编码的近似解决方案。应该执行顺序算法。威尔士-鲍威尔(Welsh-Powell)算法是一种基于顶点按度降序排列的贪婪解决方案。翻译成原始多边形的语言后,首先按照多边形重叠的其他多边形数量的降序对多边形进行排序。按顺序工作,为第一个多边形赋予初始颜色。在每个后续步骤中,尝试使用现有颜色为下一个多边形着色:即,选择一种存在的颜色已被该多边形的任何邻居使用。(有很多方法可以在可用的颜色中进行选择;尝试使用最少的一种,或者随机选择一种。)如果下一个多边形无法用现有颜色进行着色,请创建新的颜色并使用该颜色进行着色。

一旦实现了少量颜色的着色,就按颜色执行zonalstats颜色:通过构造,可以确保给定颜色的两个多边形都不会重叠。


这是中的示例代码R。(Python代码没有什么不同。)首先,我们描述所示的七个多边形之间的重叠。

七个多边形的地图

edges <- matrix(c(1,2, 2,3, 3,4, 4,5, 5,1, 2,6, 4,6, 4,7, 5,7, 1,7), ncol=2, byrow=TRUE)

也就是说,多边形1和2重叠,多边形2和3、3和4,...,1和7也重叠。

按降序对顶点排序:

vertices <- unique(as.vector(edges))
neighbors <- function(i) union(edges[edges[, 1]==i,2], edges[edges[, 2]==i,1])
nbrhoods <- sapply(vertices, neighbors)
degrees <- sapply(nbrhoods, length)
v <- vertices[rev(order(degrees))]

(粗略的)顺序着色算法使用任何重叠多边形尚未使用的最早的可用颜色:

color <- function(i) {
  n <- neighbors(i)
  candidate <- min(setdiff(1:color.next, colors[n]))
  if (candidate==color.next) color.next <<- color.next+1
  colors[i] <<- candidate
}

初始化数据结构(colorscolor.next)并应用算法:

colors <- rep(0, length(vertices))
color.next <- 1
temp <- sapply(v, color)

根据颜色将多边形分成几组:

split(vertices, colors)

本示例中的输出使用四种颜色:

$`1`
[1] 2 4

$`2`
[1] 3 6 7

$`3`
[1] 5

$`4`
[1] 1

多边形的四种颜色

它已将多边形划分为四个不重叠的组。在这种情况下,解决方案不是最优的({{3,6,5},{2,4},{1,7}}是该图的三色)。总的来说,它得到的解决方案应该不会太差。


我不确定这是否回答了问题,或者是什么问题,但这仍然是一个很好的答案。
nagytech

@Geoist有什么办法可以使插图更清楚或更清楚地说明问题?
ub

6

#whuber推荐的方法论启发了我朝两个方向发展,这是我的arcpy解决方案的一个新方向。第一个称为countOverlaps,创建两个字段“ overlaps”和“ ovlpCount”,以记录每个多边形与之重叠的多边形以及发生了多少重叠。第二个函数explodeOverlaps创建第三个字段“ expl”,该字段为每组不重叠的多边形赋予唯一的整数。然后,用户可以根据该字段导出新的fc。该过程分为两个功能,因为我认为countOverlaps工具本身可以证明是有用的。请原谅代码的草率(以及粗心的命名约定),因为这是很初步的,但是可以。还要确保“ idName” field代表具有唯一ID的字段(仅使用整数ID进行测试)。感谢whuber为我提供解决此问题所需的框架!

def countOverlaps(fc,idName):
    intersect = arcpy.Intersect_analysis(fc,'intersect')
    findID = arcpy.FindIdentical_management(intersect,"explFindID","Shape")
    arcpy.MakeFeatureLayer_management(intersect,"intlyr")
    arcpy.AddJoin_management("intlyr",arcpy.Describe("intlyr").OIDfieldName,findID,"IN_FID","KEEP_ALL")
    segIDs = {}
    featseqName = "explFindID.FEAT_SEQ"
    idNewName = "intersect."+idName

    for row in arcpy.SearchCursor("intlyr"):
        idVal = row.getValue(idNewName)
        featseqVal = row.getValue(featseqName)
        segIDs[featseqVal] = []
    for row in arcpy.SearchCursor("intlyr"):
        idVal = row.getValue(idNewName)
        featseqVal = row.getValue(featseqName)
        segIDs[featseqVal].append(idVal)

    segIDs2 = {}
    for row in arcpy.SearchCursor("intlyr"):
        idVal = row.getValue(idNewName)
        segIDs2[idVal] = []

    for x,y in segIDs.iteritems():
        for segID in y:
            segIDs2[segID].extend([k for k in y if k != segID])

    for x,y in segIDs2.iteritems():
        segIDs2[x] = list(set(y))

    arcpy.RemoveJoin_management("intlyr",arcpy.Describe(findID).name)

    if 'overlaps' not in [k.name for k in arcpy.ListFields(fc)]:
        arcpy.AddField_management(fc,'overlaps',"TEXT")
    if 'ovlpCount' not in [k.name for k in arcpy.ListFields(fc)]:
        arcpy.AddField_management(fc,'ovlpCount',"SHORT")

    urows = arcpy.UpdateCursor(fc)
    for urow in urows:
        idVal = urow.getValue(idName)
        if segIDs2.get(idVal):
            urow.overlaps = str(segIDs2[idVal]).strip('[]')
            urow.ovlpCount = len(segIDs2[idVal])
        urows.updateRow(urow)

def explodeOverlaps(fc,idName):

    countOverlaps(fc,idName)

    arcpy.AddField_management(fc,'expl',"SHORT")

    urows = arcpy.UpdateCursor(fc,'"overlaps" IS NULL')
    for urow in urows:
        urow.expl = 1
        urows.updateRow(urow)

    i=1
    lyr = arcpy.MakeFeatureLayer_management(fc)
    while int(arcpy.GetCount_management(arcpy.SelectLayerByAttribute_management(lyr,"NEW_SELECTION",'"expl" IS NULL')).getOutput(0)) > 0:
        ovList=[]
        urows = arcpy.UpdateCursor(fc,'"expl" IS NULL','','','ovlpCount D')
        for urow in urows:
            ovVal = urow.overlaps
            idVal = urow.getValue(idName)
            intList = ovVal.replace(' ','').split(',')
            for x in intList:
                intList[intList.index(x)] = int(x)
            if idVal not in ovList:
                urow.expl = i
            urows.updateRow(urow)
            ovList.extend(intList)
        i+=1

2
将此连接到我的解决方案:您的countOverlaps对应nbrhoods <- sapply(vertices, neighbors); degrees <- sapply(nbrhoods, length)于我代码中的两行:degrees是重叠计数。当然,您的代码更长,因为它反映了我的解决方案中理所当然的大多数GIS分析:即,首先确定哪些多边形重叠,最后使用解决方案输出多边形数据集。这将是一个好主意,封装图的理论计算,所以如果你找到一个更好的着色算法,它会很容易堵塞英寸
whuber

1

已经有一段时间了,但是我在自己的应用程序中使用了此代码,并且运行良好,谢谢。我重新编写了其中的一部分以对其进行更新,将其应用于行(具有公差)并显着加快了速度(下面-我在5000万个相交的缓冲区上运行它,只用了几个小时)。

def ExplodeOverlappingLines(fc, tolerance, keep=True):
        print('Buffering lines...')
        idName = "ORIG_FID"
        fcbuf = arcpy.Buffer_analysis(fc, fc+'buf', tolerance, line_side='FULL', line_end_type='FLAT')
        print('Intersecting buffers...')
        intersect = arcpy.Intersect_analysis(fcbuf,'intersect')

        print('Creating dictionary of overlaps...')
        #Find identical shapes and put them together in a dictionary, unique shapes will only have one value
        segIDs = defaultdict(list)
        with arcpy.da.SearchCursor(intersect, ['Shape@WKT', idName]) as cursor:
            x=0
            for row in cursor:
                if x%100000 == 0:
                    print('Processed {} records for duplicate shapes...'.format(x))
                segIDs[row[0]].append(row[1])
                x+=1

        #Build dictionary of all buffers overlapping each buffer
        segIDs2 = defaultdict(list)
        for v in segIDs.values():
            for segID in v:
                segIDs2[segID].extend([k for k in v if k != segID and k not in segIDs2[segID]])

        print('Assigning lines to non-overlapping sets...')
        grpdict = {}
        # Mark all non-overlapping one to group 1
        for row in arcpy.da.SearchCursor(fcbuf, [idName]):
            if row[0] in segIDs2:
                grpdict[row[0]] = None
            else:
                grpdict[row[0]] = 1

        segIDs2sort = sorted(segIDs2.items(), key=lambda x: (len(x[1]), x[0])) #Sort dictionary by number of overlapping features then by keys
        i = 2
        while None in grpdict.values(): #As long as there remain features not assigned to a group
            print(i)
            ovset = set()  # list of all features overlapping features within current group
            s_update = ovset.update
            for rec in segIDs2sort:
                if grpdict[rec[0]] is None: #If feature has not been assigned a group
                    if rec[0] not in ovset: #If does not overlap with a feature in that group
                        grpdict[rec[0]] = i  # Assign current group to feature
                        s_update(rec[1])  # Add all overlapping feature to ovList
            i += 1 #Iterate to the next group

        print('Writing out results to "expl" field in...'.format(fc))
        arcpy.AddField_management(fc, 'expl', "SHORT")
        with arcpy.da.UpdateCursor(fc,
                                   [arcpy.Describe(fc).OIDfieldName, 'expl']) as cursor:
            for row in cursor:
                if row[0] in grpdict:
                    row[1] = grpdict[row[0]]
                    cursor.updateRow(row)

        if keep == False:
            print('Deleting intermediate outputs...')
            for fc in ['intersect', "explFindID"]:
                arcpy.Delete_management(fc)

-3

在这种情况下,我通常使用以下方法:

  • 通过UNION传递Feature类;(它会在所有相交处破坏多边形)
  • 添加X,Y和Area字段并计算它们;
  • 通过X,Y,Area字段分解结果。

我相信结果将是您想要的结果,您甚至可以计算重叠的数量。不知道在性能方面是否对您更好。


2
此方法无法使您获得所需的产品,这是不重叠的最小选择系列或原始产品的唯一要素类。产品将被用于区域统计,因此保持每个要素的原始几何形状至关重要。
ndimhypervol,2012年

您说得对,抱歉。我不太清楚这个问题。在那种情况下,根据栅格的大小,我通常会将栅格转换为临时点要素类(每个像元为一个点),并在其与多边形图层之间执行空间连接。也许它是一种非常简单且对性能不友好的方法,但是可以工作并且重叠的多边形不会给您带来任何问题。
亚历山大·内托

如果我正确理解了这种空间连接的含义,那么您的第二个解决方案仍然无法使用,Alexandre,因为点和多边形之间存在多对多关系。无论如何,对于任何较大的栅格,这种基于矢量的方法都将效率极低,而对于大型栅格,将无法执行。
ub

@whuber这是一个非常缓慢的过程,这是正确的(用4284 x 3009栅格和2401多边形,双核2.8Ghz,3Gb RAM和vista在大约半小时内教我)。但是它有效,因为我已经对其进行了测试。在空间连接中,您必须使用一对一关系,并汇总栅格值(平均值,总和等)。结果将是一个矢量多边形图层,该图层与原始图层相似,但具有一个新列,该列具有与每个多边形相交的聚合栅格值。并非最佳解决方案,这对于编程技能较低的人(例如我:-)可能很有用。
亚历山大·内托
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.