给定多个以多种方式重叠的多边形,我想从这些要素中迭代导出所有不与其他多边形重叠的多边形。
该产品将具有许多不重叠的功能,将它们加在一起便构成了原始功能。
然后可以将乘积用作区域统计的输入,这比在每个多边形上迭代区域统计要快得多。
我一直试图在ArcPy中对此进行编码,但没有成功。
是否已经存在执行此操作的代码?
给定多个以多种方式重叠的多边形,我想从这些要素中迭代导出所有不与其他多边形重叠的多边形。
该产品将具有许多不重叠的功能,将它们加在一起便构成了原始功能。
然后可以将乘积用作区域统计的输入,这比在每个多边形上迭代区域统计要快得多。
我一直试图在ArcPy中对此进行编码,但没有成功。
是否已经存在执行此操作的代码?
Answers:
这是图形着色问题。
回想一下,图着色是将颜色分配给图的顶点的方式,使得没有两个共享边的顶点也将具有相同的颜色。具体而言,图的(抽象)顶点是多边形。每当两个顶点相交(作为多边形)时,两个顶点就用(无向)边连接。如果我们对问题采取任何解决方案-这是一系列(例如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
}
初始化数据结构(colors
和color.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}}是该图的三色)。总的来说,它得到的解决方案应该不会太差。
#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
countOverlaps
对应nbrhoods <- sapply(vertices, neighbors); degrees <- sapply(nbrhoods, length)
于我代码中的两行:degrees
是重叠计数。当然,您的代码更长,因为它反映了我的解决方案中理所当然的大多数GIS分析:即,首先确定哪些多边形重叠,最后使用解决方案输出多边形数据集。这将是一个好主意,封装图的理论计算,所以如果你找到一个更好的着色算法,它会很容易堵塞英寸
已经有一段时间了,但是我在自己的应用程序中使用了此代码,并且运行良好,谢谢。我重新编写了其中的一部分以对其进行更新,将其应用于行(具有公差)并显着加快了速度(下面-我在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)
在这种情况下,我通常使用以下方法:
我相信结果将是您想要的结果,您甚至可以计算重叠的数量。不知道在性能方面是否对您更好。