コストパスソリューションがありますが、自分でコーディングする必要があります。問題の画像のすべての点 に適用すると、次のようになります(計算を高速化するために少し粗くしました)。
黒いセルは周囲のポリゴンの一部です。ライトオレンジ(ショート)からブルー(ロング)までの色は、ポリゴンセルを妨害せずに見通し内トラバーサルによって到達できる最大距離(最大50セルまで)を示します。(この画像の範囲外のセルは、ポリゴンの一部として扱われます。)
データのラスター表現を使用してこれを行う効率的な方法について説明します。この表現では、すべての「周囲の」多角形セルは、たとえばゼロ以外の値を持ち、「透けて見える」可能性のあるセルはすべてゼロの値を持ちます。
ステップ1:近隣データ構造の事前計算
最初に、あるセルが別のセルをブロックすることの意味を決定する必要があります。私が見つけることができる最も公平なルールの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(1)時間で見つかりますscreen
)。ブロックされているものがない場合は、境界に達しており、(x、y)は囲まれていないと結論付けるため、計算を停止します(近隣の残りのポイントを検査する必要はありません)。
アルゴリズム中に到達した最遠の見通し距離を追跡することにより、さらに有用な情報を収集できます。これが望ましい半径より小さい場合、セルは囲まれます。そうでなければ、そうではありません。
ここでR
、このアルゴリズムのプロトタイプを。R
は再帰の実装に必要な(単純な)スタック構造をネイティブでサポートしていないため、見た目よりも長く、スタックもコーディングする必要があります。実際のアルゴリズムは、約3分の2の経路から始まり、必要なのは数十行程度です。(そしてそれらの半分は単にグリッドの端の周りの状況を処理し、近傍内の範囲外のインデックスをチェックします。これはk
、周囲の行と列でポリゴングリッドを拡張し、ポリゴングリッドを保持するために、RAMを少し増やしてインデックス範囲チェックが必要です。)
#
# 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セルまで)を示します。範囲は、距離が短い場合は薄いオレンジ色から距離が長い場合は濃い青色です。(セルの幅と高さは1ユニットです。)目に見える明らかな縞は、「川」の中央にある小さなポリゴンの「島」によって作成されます。それぞれが他のセルの長い線をブロックしています。
アルゴリズムの分析
スタック構造は、セルが囲まれていない証拠のための近傍可視性グラフの深さ優先検索を実装します。セルがどのポリゴンからも離れている場合、この検索では、半径kの円形近傍のO(k)セルのみを検査する必要があります。最悪のケースは、近傍内に散在するポリゴンセルの数が少ないが、近傍の境界に到達できない場合に発生します。これらは、O(k ^ 2)である各近傍のほとんどすべてのセルを検査する必要があります。操作。
次の動作は、発生する典型的なものです。 kの値が小さい場合、ポリゴンがグリッドの大部分を埋めない限り、ほとんどの非ポリゴンセルは明らかに囲まれておらず、アルゴリズムはO(k)のようにスケーリングされます。中間値の場合、スケーリングはO(k ^ 2)のように見えます。kが非常に大きくなると、ほとんどのセルが囲まれ、その事実は近傍全体が検査される前に決定できます。これにより、アルゴリズムの計算作業は実際的な限界に達します。この制限は、近傍半径がグリッド内の接続された最大の非多角形領域の直径に近づくと達成されます。
例として、counting
のプロトタイプにコード化されたオプションをscreen
使用して、各呼び出しで使用されるスタック操作の数を返します。これは計算の労力を測定します。次のグラフは、近傍半径の関数としてスタック操作の平均数をプロットしています。予測された動作を示します。
これを使用して、グリッド上の1300万点を評価するために必要な計算を推定できます。k = 200/5 = 40の近傍が使用されていると仮定します。その場合、平均で数百のスタック操作が必要になります(ポリゴングリッドの複雑さと、1億3千万個のポイントがポリゴンに対して配置されている場所によって異なります)。これは、効率的なコンパイル済み言語では、最大で数千の単純な数値演算を意味します。必要になります(加算、乗算、読み取り、書き込み、オフセットなど)。ほとんどのPCは、その速度で約100万ポイントの周囲を評価できます。(R
実装は、この種のアルゴリズムでは不十分であるため、はるかに遅く、プロトタイプとしか見なせません。したがって、合理的に効率的で適切な言語での効率的な実装が望まれます。C++そして、Pythonが頭に浮かびます。ポリゴングリッド全体がRAMにあると仮定すると、1百万ポイント以下の評価を1分以内で完了する可能性があります。
グリッドが大きすぎてRAMに収まらない場合、この手順をグリッドのタイル部分に適用できます。それらはk
行と列でオーバーラップする必要があるだけです。結果をモザイク化するとき、オーバーラップで最大値をとります。
その他の用途
水域の「フェッチ」は、そのポイントの「周囲性」と密接に関連しています。実際、水体の直径以上の近傍半径を使用する場合、水体のすべてのポイントに(無方向)フェッチのグリッドを作成します。より小さな近傍半径を使用することにより、すべての最高フェッチポイントで少なくともフェッチの下限を取得します。これは、一部のアプリケーションでは十分な場合があります(そして、計算の労力を大幅に削減できます)。特定の方向への「スクリーニング」関係を制限するこのアルゴリズムのバリアントは、それらの方向でフェッチを効率的に計算する1つの方法です。そのような亜種はのコードを変更する必要があることに注意してくださいscreen
。のコードpanvisibility
はまったく変わりません。