有一个成本路径解决方案,但您必须自己编写代码。 将其应用于问题中图像的每个点时,可能看起来是这样(略微加粗以加快计算速度):
黑色单元格是周围多边形的一部分。从浅橙色(短)到蓝色(长)的颜色显示了视距遍历可以达到的最大距离(最多50个像元),而不会拦截多边形像元。(此图像范围之外的任何单元格都将视为多边形的一部分。)
让我们讨论使用数据的栅格表示来实现此目的的有效方法。在此表示形式中,所有“周围”的多边形像元将具有非零值,并且任何可以“透视”的像元将具有零值。
步骤1:预计算邻域数据结构
您首先必须确定一个单元格阻止另一个单元格的含义。我能找到的最公平的规则之一是:对行和列使用整数坐标(并假设为正方形单元格),让我们考虑哪些单元格可能会从原点(0,0)的视图中阻止单元格(i,j)。我在坐标与i和j最多相差1的所有像元中,指定最接近将(i,j)连到(0,0)的线段的像元(i',j')。因为这并不总是产生唯一的解决方案(例如,当(i,j)=(1,2)时(0,1)和(1,1)都将同样有效地工作),需要一些解决关系的方法。对于关系的这种解析,最好尊重网格中圆形邻域的对称性:否定坐标或切换坐标会保留这些邻域。因此,我们可以决定哪个单元格会阻塞(i,
下面的原型代码用编写了说明此规则R
。此代码返回一个数据结构,该数据结构将方便确定网格中任意单元的“环绕度”。
screen <- function(k=1) {
#
# Returns a data structure:
# $offset is an array of offsets
# $screened is a parallel array of screened offset indexes.
# $distance is a parallel array of distances.
# The first index always corresponds to (0,0).
#
screened.by <- function(xy) {
uv <- abs(xy)
if (reversed <- uv[2] > uv[1]) {
uv <- rev(uv)
}
i <- which.min(c(uv[1], abs(uv[1]-uv[2]), uv[2]))
ij <- uv + c(floor((1-i)/3), floor(i/3)-1)
if (reversed) ij <- rev(ij)
return(ij * sign(xy))
}
#
# For each lattice point within the circular neighborhood,
# find the unique lattice point that screens it from the origin.
#
xy <- subset(expand.grid(x=(-k:k), y=(-k:k)),
subset=(x^2+y^2 <= k^2) & (x != 0 | y != 0))
g <- t(apply(xy, 1, function(z) c(screened.by(z), z)))
#
# Sort by distance from the origin.
#
colnames(g) <- c("x", "y", "x.to", "y.to")
ij <- unique(rbind(g[, 1:2], g[, 3:4]))
i <- order(abs(ij[,1]), abs(ij[,2])); ij <- ij[i, , drop=FALSE]
rownames(ij) <- 1:length(i)
#
# Invert the "screened by" relation to produce the "screened" relation.
#
# (Row, column) offsets.
ij.df <- data.frame(ij, i=1:length(i))
#
# Distances from the origin (in cells).
distance <- apply(ij, 1, function(u) sqrt(sum(u*u)))
#
# "Screens" relation (represented by indexes into ij).
g <- merge(merge(g, ij.df), ij.df,
by.x=c("x.to", "y.to"), by.y=c("x","y"))
g <- subset(g, select=c(i.x, i.y))
h <- by(g$i.y, g$i.x, identity)
return( list(offset=ij, screened=h, distance=distance) )
}
的值screen(12)
用于描述这种筛选关系:箭头从细胞指向立即筛选它们的细胞。色相按与原点的距离成比例,原点位于该邻域的中间:
这种计算速度很快,对于给定的邻域只需执行一次。例如,当在具有5 m单元的网格上观察200 m时,邻域大小将为200/5 = 40个单位。
步骤2:将计算应用于所选点
其余的方法很简单:要确定相对于此邻域数据结构,位于(x,y)(行和列坐标)处的像元是否被“包围”,请以偏移量(i,j)开始递归执行测试=(0,0)(邻域原点)。如果在(x,y)+(i,j)处的多边形网格中的值不为零,则可见性在那里被阻止。否则,我们将不得不考虑所有可能在偏移量(i,j)处被阻塞的偏移量(使用O返回的数据结构在O(1)时间中找到screen
)。如果没有任何障碍物,我们就到达了边界并得出结论:(x,y)没有被包围,因此我们停止了计算(并且不必费心去检查附近的任何剩余点)。
通过跟踪算法中达到的最远视距,我们可以收集更多有用的信息。如果小于所需的半径,则包围该单元;否则,将其包围。否则,不是。
这是R
此算法的原型。它比看起来更长,因为R
它本身不支持实现递归所需的(简单)堆栈结构,因此堆栈也必须进行编码。实际的算法大约从三分之二开始,只需要十几行。(其中一半只处理网格边缘周围的情况,检查邻域内的索引超出范围。只需将多边形网格k
按其周围的行和列扩展,消除任何重复,即可提高效率。需要进行索引范围检查,但要花更多的内存来保存多边形网格。)
#
# Test a grid point `ij` for a line-of-sight connection to the perimeter
# of a circular neighborhood.
# `xy` is the grid.
# `counting` determines whether to return max distance or count of stack ops.
# `perimeter` is the assumed values beyond the extent of `xy`.
#
# Grid values of zero admit light; all others block visibility
# Returns maximum line-of-sight distance found within `nbr`.
#
panvisibility <- function(ij, xy, nbr=screen(), counting=FALSE, perimeter=1) {
#
# Implement a stack for the algorithm.
#
count <- 0 # Stack count
stack <- list(ptr=0, s=rep(NA, dim(nbr$offset)[1]))
push <- function(x) {
n <- length(x)
count <<- count+n # For timing
stack$s[1:n + stack$ptr] <<- x
stack$ptr <<- stack$ptr+n
}
pop <- function() {
count <<- count+1 # For timing
if (stack$ptr <= 0) return(NULL)
y <- stack$s[stack$ptr]
#stack$s[stack$ptr] <<- NA # For debugging
stack$ptr <<- stack$ptr - 1
return(y)
}
#
# Initialization.
#
m <- dim(xy)[1]; n <- dim(xy)[2]
push(1) # Stack the *indexes* of nbr$offset and nbr$screened.
dist.max <- -1
#
# The algorithm.
#
while (!is.null(i <- pop())) {
cell <- nbr$offset[i, ] + ij
if (cell[1] <= 0 || cell[1] > m || cell[2] <= 0 || cell[2] > n) {
value <- perimeter
} else {
value <- xy[cell[1], cell[2]]
}
if (value==0) {
if (nbr$distance[i] > dist.max) dist.max <- nbr$distance[i]
s <- nbr$screened[[paste(i)]]
if (is.null(s)) {
#exited = TRUE
break
}
push(s)
}
}
if (counting) return ( count )
return(dist.max)
}
在此示例中,多边形单元为黑色。对于非多边形单元,颜色给出最大视线距离(最多50个单元),范围从浅橙色(短距离)到深蓝色(最长距离)。(单元格是一个宽且高的单元。)明显可见的条纹是由位于“河”中间的小多边形“岛”形成的:每个单元都阻塞了其他单元格的长线。
算法分析
堆栈结构实现了邻居可见度图的深度优先搜索,以查找未包围单元的证据。如果像元远离任何多边形,则此搜索仅需要检查半径为k的圆形邻域的O(k)个像元。最坏的情况发生在邻域内有少量分散的多边形像元,但即使如此,邻域的边界也无法完全到达时:这需要检查每个邻域中几乎所有像元,即O(k ^ 2)操作。
下面的行为是遇到的典型现象。 对于较小的k值,除非多边形填充了大部分网格,否则大多数非多边形像元显然不会被包围,并且算法的缩放比例类似于O(k)。对于中间值,缩放比例开始看起来像O(k ^ 2)。随着k变得非常大,大多数单元将被包围,并且可以在检查整个邻域之前确定该事实:该算法的计算量因此达到了实际极限。当邻域半径接近网格中最大的连接非多边形区域的直径时,可以达到此限制。
例如,我使用counting
编码到原型中的选项screen
来返回每个调用中使用的堆栈操作数。这测量了计算量。下图绘制了堆栈操作数的平均值与邻域半径的关系。它表现出预测的行为。
我们可以使用它来估算在网格上评估1300万点所需的计算量。假设使用k = 200/5 = 40的邻域。那么平均将需要数百次堆栈操作(取决于多边形网格的复杂性以及相对于多边形的1300万个点的位置),这意味着在高效的编译语言中,最多只有几千个简单的数字运算将是必需的(加,乘,读,写,偏移量等)。大多数PC能够以该速率评估大约一百万个点的包围度。(R
实现要慢得多,因为它不适合这种算法,这就是为什么只能将其视为原型的原因。)因此,我们可能希望以合理有效和适当的语言(C ++)进行有效实现和Python浮现在脑海中- 假设整个多边形网格都位于RAM中,则可以在一分钟或更短的时间内完成1300万个点的评估。
当网格太大而无法放入RAM时,可以将此过程应用于网格的平铺部分。它们仅需按k
行和列重叠;拼接结果时,在重叠处取最大值。
其他应用
水体的“获取”与其点的“环绕性”密切相关。实际上,如果我们使用等于或大于水体直径的邻域半径,我们将在水体中的每个点处创建一个(非定向)获取的网格。通过使用较小的邻域半径,我们至少将在所有最高获取点处获得较低的获取下限,这在某些应用程序中可能已经足够好了(并且可以大大减少计算量)。该算法的一种变体,其将“筛选依据”关系限制在特定方向上,将是一种有效地计算这些方向上的提取的方法。请注意,此类变体需要修改screen
; 的代码;的代码panvisibility
完全不变。