Bir maliyet yolu çözümü var, ancak bunu kendiniz kodlamanız gerekecek. Sorudaki görüntüdeki her noktaya uygulandığında neye benzeyebileceği aşağıda (hesaplamaları hızlandırmak için biraz kabalaştırılmıştır):
Kara hücreler, çevreleyen çokgenlerin parçalarıdır. Açık turuncu (kısa) ile mavi (uzun) arasında değişen renkler, poligon hücrelerini engellemeden görüş hattı geçişi ile ulaşılabilecek maksimum mesafeyi (maksimum 50 hücreye kadar) gösterir. (Bu görüntünün kapsamı dışındaki herhangi bir hücre, çokgenlerin bir parçası olarak ele alınır.)
Verilerin raster temsilini kullanarak bunu yapmanın etkili bir yolunu tartışalım. Bu gösterimde, tüm "çevreleyen" poligonal hücrelerin, örneğin sıfır olmayan değerlere sahip olacağı ve "içinden geçilebilen" herhangi bir hücrenin sıfır değeri olacaktır.
1. Adım: Bir mahalle veri yapısının önceden hesaplanması
Önce bir hücrenin diğerini engellemesinin ne anlama geldiğine karar vermelisiniz. Bulabildiğim en adil kurallardan biri şudur: satırlar ve sütunlar için integral koordinatlar kullanarak (ve kare hücreleri varsayarak), hangi hücrelerin (i, j) hücresini başlangıç noktasındaki (0,0) görünümden engelleyebileceğini düşünelim. Koordinatları i ve j'den en fazla 1 farklı olan tüm hücreler arasında (i, j) 'den (0,0)' a bağlanan çizgi segmentine en yakın hücreyi (i ', j') belirlerim. Çünkü bu her zaman değil benzersiz bir çözüm üretir (örneğin, (i, j) = (1,2) hem (0,1) hem de (1,1) eşit derecede iyi çalışır), bağları çözmek için bazı araçlar gereklidir. Bağların bu çözümünün ızgaralardaki dairesel mahallelerin simetrilerine saygı duyması iyi olurdu: koordinatı reddetmek veya koordinatları değiştirmek bu mahalleleri korur. Bu nedenle hangi hücrelerin bloke olduğuna karar verebiliriz (i,
Bu kuralı açıklayan şu prototip kodudur R
. Bu kod, bir ızgaradaki rasgele hücrelerin "çevrelenmesini" belirlemek için uygun olacak bir veri yapısı döndürü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) )
}
Değeri, screen(12)
bu tarama ilişkisinin bu tasvirini üretmek için kullanıldı: oklar hücrelerden onları hemen tarayanlara işaret ediyor. Tonlar, bu mahallenin ortasında bulunan menşe mesafesine göre orantılıdır:
Bu hesaplama hızlıdır ve belirli bir mahalle için sadece bir kez yapılması gerekir. Örneğin, 5 m hücreli bir ızgarada 200 m'ye bakıldığında, mahalle büyüklüğü 200/5 = 40 birim olacaktır.
Adım 2: Hesaplamanın seçilen noktalara uygulanması
Gerisi basittir: (x, y) 'de (satır ve sütun koordinatlarında) bulunan bir hücrenin bu mahalle veri yapısına göre "çevrili" olup olmadığını belirlemek için, testi (i, j) = (0,0) (mahalle menşei). (X, y) + (i, j) 'deki çokgen ızgaradaki değer sıfır değilse, orada görünürlük engellenir. Aksi takdirde, ofset (i, j) 'de engellenmiş olabilecek tüm ofsetleri (döndürülen veri yapısı kullanılarak O (1) zamanında bulunur) dikkate almamız gerekir screen
. Engellenen hiçbir şey yoksa, çevreye ulaştık ve (x, y) 'nin çevrelenmediği sonucuna vardık, bu nedenle hesaplamayı durduruyoruz (ve mahallede kalan noktaları denetlemeye zahmet etmiyoruz).
Algoritma sırasında ulaşılan en uzak görüş mesafesini takip ederek daha da faydalı bilgiler toplayabiliriz. Bu istenen yarıçaptan küçükse, hücre çevrilidir; aksi halde değildir.
İşte R
bu algoritmanın bir prototipi. Göründüğünden daha uzun, çünkü R
özyinelemeyi uygulamak için gerekli (basit) yığın yapısını yerel olarak desteklemiyor, bu nedenle bir yığının da kodlanması gerekiyor. Gerçek algoritma, yolun yaklaşık üçte ikisini başlatır ve sadece bir düzine çizgiye ihtiyaç duyar. (Ve bu yarısı sadece mahalle içinde dışarı aralık endekslerinin kontrol, ızgara kenarında durumu idare. Bu daha verimli hale getirilebilir basitçe tarafından poligon ızgara genişleterek k
onun çevresinde satır ve sütun herhangi ortadan kaldırarak çokgen ızgarasını tutmak için biraz daha fazla RAM pahasına dizin aralığı kontrolüne ihtiyaç var.)
#
# 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)
}
Bu örnekte, çokgen hücreler siyahtır. Renkler, çokgen olmayan hücreler için kısa mesafeler için açık turuncudan en uzun mesafeler için koyu maviye kadar maksimum görüş hattı mesafesini (50 hücreye kadar) verir. (Hücreler bir birim genişliğinde ve yüksektir.) Görünür şekilde belirgin çizgiler "nehrin" ortasındaki küçük çokgen "adalar" tarafından oluşturulur: her biri diğer hücrelerin uzun bir çizgisini engeller.
Algoritmanın analizi
Yığın yapısı uygulayan, bir hücre olduğunu kanıtlar mahalle görüş grafiğinin bir derinlik ilk arama değil çevrili. Hücrelerin herhangi bir çokgenden uzak olduğu durumlarda, bu arama için yarıçap-k dairesel bir mahalle için sadece O (k) hücrelerinin incelenmesi gerekir. En kötü durumlar mahallede az sayıda dağınık poligon hücresi olduğunda, ancak mahallenin sınırına tam olarak ulaşılamaması durumunda ortaya çıkar: bunlar her mahalledeki neredeyse tüm hücrelerin incelenmesini gerektirir, bu bir O (k ^ 2) operasyon.
Aşağıdaki davranış, karşılaşılacaklar için tipiktir. Küçük k değerleri için, çokgenler ızgaranın çoğunu doldurmadıkça, çokgen olmayan hücrelerin çoğu açık bir şekilde tamamlanamaz ve algoritma O (k) gibi ölçeklenir. Ara değerler için ölçeklendirme O (k ^ 2) gibi görünmeye başlar. K gerçekten büyüdükçe, çoğu hücre çevrelenecek ve bu gerçek tüm mahalle incelenmeden çok önce belirlenebilir: algoritmanın hesaplama çabası pratik bir sınıra ulaşır. Bu sınır, mahalle yarıçapı ızgaradaki en büyük bağlı poligonal olmayan bölgelerin çapına yaklaştığında elde edilir.
Örnek olarak, her çağrıda kullanılan yığın işlemlerinin sayısını döndürmek için counting
prototipine kodlanmış seçeneği kullanıyorum screen
. Bu hesaplama çabasını ölçer. Aşağıdaki grafik, mahalle yarıçapının bir fonksiyonu olarak ortalama yığın ops sayısını göstermektedir. Tahmin edilen davranışı gösterir.
Bunu, bir tablodaki 13 milyon noktayı değerlendirmek için gereken hesaplamayı tahmin etmek için kullanabiliriz. Varsayalım ki k = 200/5 = 40 mahallesi kullanılıyor. Daha sonra ortalama olarak birkaç yüz yığın operasyonuna ihtiyaç duyulacaktır (çokgen ızgarasının karmaşıklığına ve çokgenlere göre 13 milyon noktanın bulunduğu yere), verimli bir derlenmiş dilde en fazla birkaç bin basit sayısal işlem anlamına gelir. (ekleme, çarpma, okuma, yazma, ofset vb.) gerekir. Çoğu bilgisayar bu oranda yaklaşık bir milyon noktanın çevresini değerlendirebilecektir. (R
uygulama çok daha yavaştır, çünkü bu tür bir algoritmada zayıftır, bu yüzden sadece bir prototip olarak kabul edilebilir.) Buna göre, makul derecede verimli ve uygun bir dilde etkili bir uygulamanın - C ++ ve Python akla geliyor - tüm poligon ızgarasının RAM'de bulunduğu varsayılarak , bir dakika veya daha kısa sürede 13 milyon noktanın değerlendirmesini tamamlayabilir .
Bir ızgara RAM'e sığmayacak kadar büyük olduğunda, bu prosedür ızgaranın döşenmiş kısımlarına uygulanabilir. Yalnızca k
satır ve sütunlarla çakışmalıdırlar ; sonuçları mozaiği yaparken üst üste gelenleri alın.
Diğer uygulamalar
Bir su kütlesinin "getirilmesi", noktalarının "çevrelenmesi" ile yakından ilişkilidir. Aslında, su kütlesinin çapına eşit veya daha büyük bir mahalle yarıçapı kullanırsak, su kütlesinin her noktasında (yönsüz) getirme ızgarası oluşturacağız. Daha küçük bir mahalle yarıçapı kullanarak, en azından getirilen tüm noktalarda getirme için en azından daha düşük bir sınır elde edeceğiz, bu da bazı uygulamalarda yeterince iyi olabilir (ve hesaplama çabalarını önemli ölçüde azaltabilir). Bu algoritmanın belirli yönlerle "taraması yapılan" ilişkiyi sınırlayan bir varyantı, bu yönlerde getirmeyi verimli bir şekilde hesaplamanın bir yolu olacaktır. Bu tür değişkenlerin kodunun değiştirilmesini gerektirdiğini unutmayın screen
; kodu panvisibility
hiç değişmez.