如何使用arcpy创建定向缓冲区?


9

我想使用arcpy为我的shapefile中的每个多边形创建一个定向缓冲区。通过定向,我的意思是我有两个角度a1和a2约束缓冲区的方向。如下图所示: 在此处输入图片说明

有任何想法吗?


3
人们将需要更多有关角度的信息。您要从哪个轴测量角度?顺时针还是逆时针?如何在多边形上定位每个角度?我们要处理哪种多边形?(圆不是多边形。)
保罗

1
+1 @保罗,但我认为一个圈是一个多边形,直到我读这个
PolyGeo

也+1!我用圆圈轻松地说明了问题。多边形是对eCognition进行分割的结果,然后进行分类以识别类别。角度a1和a2从分段卫星图像的照明方位角得出。在该示例中,方位角将等于0,a1和a2等于0 +/- 15°(任意固定为15°)。
WAF 2013年

2
@PolyGeo“ Polygon”在GIS中的使用与数学中的使用略有不同。在此,它是指(二维)区域或其封闭区域数字表示。区域通常(但不总是)由多边形近似表示但是-由于我们知道我们的计算机表示形式只是近似值-我们删除“近似”并仅使用“多边形”。
ub

Answers:


20

摘要

该答案将问题置于更大的范围内,描述了一种适用于特征的shapefile表示的有效算法(如点的“向量”或“线串”),显示了其应用的一些示例,并提供了使用或移植到其中的工作代码GIS环境。

背景

这是形态膨胀的一个例子 概括地说,扩张会将区域的点“扩散”到其邻域中。结束点的集合就是“膨胀”。GIS中的应用程序很多:对火势蔓延,文明运动,植物蔓延进行建模等。

从数学上讲,并且非常有用(但有用),膨胀使黎曼流形(例如平面,球体或椭球体)中的一组点扩散。在这些点上,切线束的子集规定了扩展。这意味着在每个点上都给出了一组向量(方向和距离)(我称之为“邻居”);这些向量中的每一个都描述了一条从其基点开始的测地路径。基点“扩展”到所有这些路径的末端。(有关图像处理中通常使用的“膨胀”的更有限定义,请参阅Wikipedia文章。扩散函数称为指数图)。 在微分几何中。)

特征的“缓冲”是这种膨胀的最简单的例子之一:在特征的每个点周围(至少在概念上)创建一个恒定半径(缓冲半径)的圆盘。这些磁盘的并集是缓冲区。

这个问题要求计算稍微复杂一点的膨胀,其中仅在给定的角度范围内(即,在圆形扇区内)才允许扩散。这仅对不包含明显弯曲表面的特征(例如,球体或椭圆体上的小特征或平面中的任何特征)有意义。当我们在飞机上工作时,将所有扇区指向同一方向也很有意义。(但是,如果我们对风的传播进行建模,则我们希望这些扇区随风而定,并且其大小也可能随风速而变化:这是我给出的一般性扩散动机之一。 )(在类似椭圆体的曲面上,通常不可能将所有扇区都定向为“相同”方向。)

在以下情况下,膨胀相对容易计算:

  • 其特点是在平面(也就是我们正在扩张一个地图的功能,希望地图是相当准确)。

  • 膨胀将是恒定的:在特征的每个点处的扩展将发生在相同方向的一致邻域内。

  • 这个共同的邻居是凸的。 凸度极大地简化并加速了计算。

这个问题适合于这样的特殊情况:它要求圆形多边形对任意多边形进行扩张,这些圆形多边形的原点(它们从其来的磁盘的中心)位于基点上。如果这些扇形的跨度不超过180度,它们将是凸形的。(较大的扇区始终可以分为两个凸扇区;两个较小的膨胀的并集将提供所需的结果。)


实作

因为我们正在执行欧几里德计算-在平面中进行扩展-我们仅通过扩张邻域平移到该点就可以扩张该点。(为了做到这一点,社区需要一个起点这将对应于基点。例如,此问题中扇区的原点是形成它们的圆的中心。这个起源恰好位于部门的边界上。在标准GIS缓冲操作中,邻域是一个以原点为中心的圆。现在,原点位于圆的内部。在计算上选择原点并不重要,因为原点的变化只会改变整个膨胀,但是在对自然现象进行建模方面可能很重要。sector以下代码中的函数说明了如何指定原点。)

扩展线段可能很棘手,但是对于凸邻域,我们可以将两个端点的扩展与并精心选择的平行四边形结合起来创建扩展。(出于空间的考虑,我不会停下来证明这样的数学断言,而是鼓励读者尝试自己的证明,因为这是一种有见地的练习。)这是使用三个扇区的插图(以粉红色显示)。它们具有单位半径,并且角度在标题中给出。线段本身的长度为2,为水平线,并以黑色显示:

细分膨胀

通过定位仅在垂直方向上与线段尽可能远的粉红色点,可以找到平行四边形。这沿着平行于线段的线给出了两个较低的点和两个较高的点。我们只需要将四个点连接成一个平行四边形(以蓝色显示)。注意,在右侧,即使扇形本身只是一个线段(而不是一个真实的多边形),这还是有道理的:在那里,线段上的每个点都已经向北以171度以东的方向平移了一段距离从0到1。这些端点的集合是所示的平行四边形。该计算的详细信息显示在以下代码中buffer定义的函数dilate.edges中。

为了扩大折线,我们形成了形成折线的点和线段的扩张的并集。最后两行dilate.edges执行此循环。

扩展多边形需要包括多边形的内部以及其边界的扩展。(此断言对膨胀邻域作了一些假设。一个是所有邻域都包含点(0,0),这保证了多边形包含在其膨胀中。对于可变邻域,它还假设任何内部空间的膨胀多边形的点不会超出边界点的扩张范围。对于恒定邻域,就是这种情况。)

让我们看一下如何工作的一些示例,首先是一个九边形(选择以显示细节),然后是一个圆形(被选择为与问题中的插图匹配)。这些示例将继续使用相同的三个邻域,但缩小到1/3的半径。

九边形的膨胀

在此图中,多边形的内部为灰色,点膨胀(扇形)为粉红色,边缘膨胀(平行四边形)为蓝色。

圆的膨胀

“圆”实际上只是一个60角,但很好地近似了一个圆。


性能

当基本特征由N个点表示,扩张邻域由M个点表示时,该算法需要O(N M)的努力。它必须通过简化联合中的顶点和边缘的混乱来完成,这可能需要O(N M log(N M))的努力:这是GIS要做的;我们不必编写程序。

对于凸形基础特征,计算量可以提高到O(M + N)(因为您可以通过适当地合并描述原始两个形状的边界的顶点列表来确定如何绕新边界行进)。也不需要任何后续清洁。

当您随着围绕基本要素的进展而逐渐扩大扩张邻域的大小和/或方向时,可以从其端点的扩张联合的凸包中近似逼近边缘的扩张。如果两个膨胀邻域具有M1和M2点,则可以使用Shamos&Preparata,计算几何中描述的算法,以O(M1 + M2)的努力来找到。因此,令K = M1 + M2 + ... + M(N)为N个膨胀邻域中的顶点总数,我们可以计算O(K * log(K))时间的膨胀。

如果我们想要的只是一个简单的缓冲区,为什么我们要解决这样的概括?对于地球上的大型要素,实际上具有恒定大小的膨胀邻域(例如磁盘)在执行这些计算的地图上可能具有变化的大小。因此,我们获得了一种对椭圆体进行精确计算的方法,同时继续享受欧几里得几何的所有优点。


这些示例都是使用该R原型制作的,可以轻松地移植到您喜欢的语言(Python,C ++等)上。在结构上,它与该答案中报告的分析相平行,因此不需要单独的说明。注释阐明了一些细节。

(可能有趣的是,三角计算用于创建示例要素(即正多边形)和扇形。膨胀计算的任何部分均不需要任何三角。)

#
# Dilate the vertices of a polygon/polyline by a shape.
#
dilate.points <- function(p, q) {
  # Translate a copy of `q` to each vertex of `p`, resulting in a list of polygons.
  pieces <- apply(p, 1, function(x) list(t(t(q)+x)))
  lapply(pieces, function(z) z[[1]]) # Convert to a list of matrices
}
#
# Dilate the edges of a polygon/polyline `p` by a shape `q`. 
# `p` must have at least two rows.
#
dilate.edges <- function(p, q) {
  i <- matrix(c(0,-1,1,0), 2, 2)       # 90 degree rotation
  e <- apply(rbind(p, p[1,]), 2, diff) # Direction vectors of the edges
  # Dilate a single edge from `x` to `x+v` into a parallelogram
  # bounded by parts of the dilation shape that are at extreme distances
  # from the edge.
  buffer <- function(x, v) {
    y <- q %*% i %*% v # Signed distances orthogonal to the edge
    k <- which.min(y)  # Find smallest distance, then the largest *after* it
    l <- (which.max(c(y[-(1:k)], y[1:k])) + k-1) %% length(y)[1] + 1
    list(rbind(x+q[k,], x+v+q[k,], x+v+q[l,], x+q[l,])) # A parallelogram
  }
  # Apply `buffer` to every edge.
  quads <- apply(cbind(p, e), 1, function(x) buffer(x[1:2], x[3:4]))
  lapply(quads, function(z) z[[1]]) # Convert to a list of matrices
}
#----------------------- (This ends the dilation code.) --------------------------#
#
# Display a polygon and its point and edge dilations.
# NB: In practice we would submit the polygon, its point dilations, and edge 
#     dilations to the GIS to create and simplify their union, producing a single
#     polygon.  We keep the three parts separate here in order to illustrate how
#     that polygon is constructed.
#
display <- function(p, d.points, d.edges, ...) {
  # Create a plotting region covering the extent of the dilated figure.
  x <- c(p[,1], unlist(lapply(c(d.points, d.edges), function(x) x[,1])))
  y <- c(p[,2], unlist(lapply(c(d.points, d.edges), function(x) x[,2])))
  plot(c(min(x),max(x)), c(min(y),max(y)), type="n", asp=1, xlab="x", ylab="y", ...)
  # The polygon itself.
  polygon(p, density=-1, col="#00000040")
  # The dilated points and edges.
  plot.list <- function(l, c) lapply(l, function(p) 
                  polygon(p, density=-1, col=c, border="#00000040"))
  plot.list(d.points, "#ff000020")
  plot.list(d.edges, "#0000ff20")
  invisible(NULL) # Doesn't return anything
}
#
# Create a sector of a circle.
# `n` is the number of vertices to use for approximating its outer arc.
#
sector <- function(radius, arg1, arg2, n=1, origin=c(0,0)) {
  t(cbind(origin, radius*sapply(seq(arg1, arg2, length.out=n), 
                  function(a) c(cos(a), sin(a)))))
}
#
# Create a polygon represented as an array of rows.
#
n.vertices <- 60 # Inscribes an `n.vertices`-gon in the unit circle.
angles <- seq(2*pi, 0, length.out=n.vertices+1)
angles <- angles[-(n.vertices+1)]
polygon.the <- cbind(cos(angles), sin(angles))
if (n.vertices==1) polygon.the <- rbind(polygon.the, polygon.the)
#
# Dilate the polygon in various ways to illustrate.
#
system.time({
  radius <- 1/3
  par(mfrow=c(1,3))
  q <- sector(radius, pi/12, 2*pi/3, n=120)
  d.points <- dilate.points(polygon.the, q)
  d.edges <- dilate.edges(polygon.the, q)
  display(polygon.the, d.points, d.edges, main="-30 to 75 degrees")

  q <- sector(radius, pi/3, 4*pi/3, n=180)
  d.points <- dilate.points(polygon.the, q)
  d.edges <- dilate.edges(polygon.the, q)
  display(polygon.the, d.points, d.edges, main="-150 to 30 degrees")

  q <- sector(radius, -9/20*pi, -9/20*pi)
  d.points <- dilate.points(polygon.the, q)
  d.edges <- dilate.edges(polygon.the, q)
  display(polygon.the, d.points, d.edges, main="171 degrees")
})

N = 60,M = 121(左),M = 181(中),M = 2(右),此示例的计算时间(根据上图)为四分之一秒。但是,大多数是用于显示。通常,此R代码每秒将处理大约N M = 150万次(只需花费0.002秒左右即可完成所示的所有示例计算)。然而,产品M N的出现意味着通过一个详细的邻域对许多图形或复杂图形进行放大可能会花费大量时间,因此请注意!在解决大问题之前,先确定较小问题的时间基准。在这种情况下,人们可能看基于光栅的解决方案(这是很多容易实现,主要是要求只是一个单一的邻里计算。)


哇,这很详细,很有趣。我没想到会减少。
保罗

1

这相当广泛,但是您可以:

  1. 缓冲原始多边形
  2. 找到要在多边形边界上创建的“定向”射线的原点(某种切点?)
  3. 使用问题注释中讨论的角度,从该点创建/延伸一条线到缓冲区之外的距离。
  4. 将该线与缓冲区和原始多边形相交。可以与3)同时使用适当的args进行扩展。
  5. 从多边形的结果集中提取新的“定向缓冲区”多边形

我相信OP表示“定向缓冲区”,即每种形状通过一个圆弧形的形态膨胀。(此描述立即提供了光栅解决方案;但是由于shapefile是矢量格式,因此需要矢量解决方案。这样做很棘手。)
whuber

希望OP将澄清这一点。我基于图形进入了思路,但这并不总是最安全的。无论如何,尽管可以相对于计算位置放置不规则形状的像元形状(我在gridedit中做了它……我感觉很旧!),但我认为矢量解决方案会更清洁/充分利用Arc的矢量功能。不管数据模型如何,通用方法可能都是相似的。可能会为光栅侧的用户提供更多编码。
罗兰

实际上,在光栅面不需要编码:-)。它可以通过多种方式完成,包括具有适当定义的邻域的焦点统计。我同意向量解决方案在这里更可取:更清晰,更精确。但是,对于庞大或复杂的数据集,它可能会陷入困境,而栅格解决方案将很快,因此,始终值得一去。
ub

浪费了思考调焦器的问题,但不确定OP的形状和角度是否很难组合成单个邻域
罗兰

邻居仅需描述扇区,这很容易做到
ub
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.