Існує рішення про витрати, але вам доведеться його кодувати самостійно. Ось як це може виглядати при застосуванні до кожної точки зображення у запитанні (трохи огрубіло, щоб прискорити обчислення):
Чорні клітини є частинами навколишніх багатокутників. Кольори, починаючи від світло-оранжевого (короткий) до синього (довгий), показують максимальну відстань (максимум до 50 комірок), яку можна досягти шляхом обходу прямого огляду, не перехоплюючи комірки полігону. (Будь-яка клітина поза рамками цього зображення розглядається як частина полігонів.)
Давайте обговоримо ефективний спосіб зробити це за допомогою растрового подання даних. У цьому поданні всі "навколишні" полігональні комірки матимуть, скажімо, ненульові значення, і будь-яка комірка, яку можна "побачити крізь", матиме нульове значення.
Крок 1: Попередньо обчисливши структуру даних про сусідство
Спочатку ви повинні вирішити, що означає одна клітинка блокувати іншу. Одне з найсправедливіших правил, яке я можу знайти, це таке: використовуючи інтегральні координати рядків і стовпців (і припускаючи квадратні осередки), давайте розглянемо, які комірки можуть блокувати клітинку (i, j) з виду на початок (0,0). Я призначаю комірку (i ', j'), яка є найближчою до відрізка лінії, що з'єднує (i, j) до (0,0), серед усіх комірок, координати яких відрізняються від i та j максимум 1. Тому що це не завжди отримати унікальне рішення (наприклад, при (i, j) = (1,2) і (0,1), і (1,1) будуть однаково добре працювати), потрібні певні засоби для вирішення зв'язків. Було б непогано, щоб ця резолюція зв'язків дотримувалася симетрії кругових мікрорайонів у сітках: заперечення ні координат, ні перемикання координат зберігає ці мікрорайони. Тому ми можемо вирішити, які клітини блокувати (я,
Ілюструючи це правило, написаний наступний код прототипу 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)
було використано для створення цього зображення цього екраніального відношення: стрілки вказують від комірок до тих, які негайно їх екранують. Відтінки пропорційні відстані до походження, яке знаходиться в середині цього мікрорайону:
Цей обчислення є швидким і його потрібно робити лише один раз для даного мікрорайону. Наприклад, якщо ви дивитесь 200 м на сітку з 5 м осередків, розмір мікрорайону складе 200/5 = 40 одиниць.
Крок 2: Застосування обчислень до вибраних точок
Інше зрозуміло: щоб визначити, чи "комірка", розташована у (x, y) (у координатах рядків і стовпців) "оточена" щодо цієї структури даних про сусідство, виконайте тест рекурсивно, починаючи зі зміщення (i, j) = (0,0) (сусідське походження). Якщо значення в сітці багатокутника при (x, y) + (i, j) ненульове, то видимість там блокується. В іншому випадку нам доведеться розглянути всі компенсації, які могли бути заблоковані при зміщенні (i, j) (які знаходяться в 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 комірок) для неополігональних комірок, починаючи від світло-оранжевого на короткі відстані до темно-синього на найдовші відстані. (Клітини - одна одиниця завширшки і висока.) Помітно очевидні смуги створюються невеликими багатокутниками «островами» посеред «річки»: кожна блокує довгий рядок інших комірок.
Аналіз алгоритму
Структура стека реалізує перший глибинний графік видимості сусідства для підтвердження того, що клітина не оточена. Там, де клітини знаходяться далеко від будь-якого багатокутника, цей пошук потребує огляду лише осередків O (k) на окружність радіуса-k. Найгірші випадки трапляються, коли всередині мікрорайону є невелика кількість розсіяних полігонів, але навіть межі мікрорайону неможливо досягти: для цього потрібен огляд майже всіх комірок у кожному районі, що є O (k ^ 2) операція.
Наступна поведінка характерна для того, з чим зіткнуться. Для малих значень k, якщо полігони не заповнюють більшу частину сітки, більшість неолігональних комірок буде очевидно незатоплена, а алгоритм масштабується як O (k). Для проміжних значень масштабування починає виглядати як O (k ^ 2). Оскільки k стає дійсно великим, більшість осередків будуть оточені, і цей факт можна визначити задовго до того, як буде перевірено все околиці: таким чином обчислювальні зусилля алгоритму досягають практичної межі. Ця межа досягається, коли радіус сусідства наближається до діаметра найбільших з'єднаних неополігональних областей у сітці.
Як приклад, я використовую counting
опцію, закодовану в прототипі, screen
щоб повернути кількість операцій стека, використовуваних у кожному виклику. Це вимірює обчислювальні зусилля. Наступний графік зображує середню кількість ops стека як функцію радіусу сусідства. Він демонструє передбачувану поведінку.
Ми можемо використати це для оцінки обчислень, необхідних для оцінки 13 мільйонів балів у сітці. Припустимо, що використовується околиця k = 200/5 = 40. Тоді в середньому знадобиться кілька сотень операцій стека (залежно від складності сітки багатокутника та де 13 мільйонів точок розташовані відносно багатокутників), маючи на увазі, що в ефективній компільованій мові максимум кілька тисяч простих числових операцій знадобиться (додавати, множувати, читати, писати, зміщувати тощо). Більшість ПК зможуть оцінити оточеність близько мільйона балів за такою швидкістю. (TheR
реалізація набагато, набагато повільніше, ніж це, тому що вона погана при такому алгоритмі, через що її можна вважати лише прототипом. Відповідно, ми можемо сподіватися, що ефективна реалізація на досить ефективній та відповідній мові - C ++ і Python приходять до тями - могли завершити оцінку 13 мільйонів балів за хвилину або менше, якщо припустити, що вся полігонна сітка знаходиться в оперативній пам'яті.
Коли сітка занадто велика, щоб вміститися в оперативну пам’ять, цю процедуру можна застосувати до плиткових частин сітки. Вони повинні лише перекриватися k
рядками та стовпцями; приймайте максимуми при перекриттях при мозаїзації результатів.
Інші програми
«Витягнути» з тіла води тісно пов'язана з «surroundedness» його точок. Насправді, якщо ми будемо використовувати радіус сусідства, рівний або більший діаметру водойми, ми створимо сітку (непрямої) плоди у кожній точці водного тіла. Використовуючи менший радіус сусідства, ми принаймні отримаємо нижню межу для вибору у всіх найвищих точках, що в деяких додатках може бути досить хорошим (і може значно зменшити обчислювальні зусилля). Варіант цього алгоритму, який обмежує відношення "екранізовано" на конкретні напрямки, був би одним із способів ефективного обчислення результатів у цих напрямках. Зауважте, що такі варіанти вимагають зміни коду для screen
; код для цього panvisibility
взагалі не змінюється.