özet
Bu cevap, soruyu daha büyük bir bağlama yerleştirir, özelliklerin şekil dosyası temsili için geçerli olan etkili bir algoritma açıklar (noktaların "vektörleri" veya "linestrings" olarak), uygulamasının bazı örneklerini gösterir ve kullanım veya bir CBS ortamı.
Arka fon
Bu morfolojik genişlemeye bir örnektir . Genel olarak, bir genişleme bir bölgenin noktalarını mahallelerine "yayar"; kurdukları noktaların toplanması "genişleme" dir. CBS'deki uygulamalar çoktur: yangının yayılmasını, medeniyetlerin hareketini, bitkilerin yayılmasını ve çok daha fazlasını modellemek.
Matematiksel olarak ve çok büyük (ama kullanışlı) bir genellikte, bir dilatasyon Riemann manifoldunda (düzlem, küre veya elipsoid gibi) bir dizi noktaya yayılır . Yayılma, bu noktalarda teğet demetinin bir alt kümesi tarafından belirlenir . Bu, noktaların her birinde bir dizi vektörün (yönler ve mesafeler) verildiği anlamına gelir (buna "mahalle" diyorum); bu vektörlerin her biri temel noktasında başlayan bir jeodezik yolu açıklar . Temel nokta tüm bu yolların uçlarına "yayılır". (Geleneksel olarak görüntü işlemede kullanılan "dilatasyon" un çok daha sınırlı tanımı için Wikipedia makalesine bakın . Yayma fonksiyonu üstel harita olarak bilinir diferansiyel geometride.)
Bir özelliğin "tamponlanması" , böyle bir genişlemenin en basit örneklerinden biridir: özelliğin her noktası etrafında sabit yarıçaplı bir disk (tampon yarıçapı) oluşturulur (en azından kavramsal olarak). Bu disklerin birleşimi arabellektir.
Bu soru, yayılmanın sadece belirli bir açı aralığında (yani dairesel bir sektör içinde) gerçekleşmesine izin verildiği biraz daha karmaşık bir genişlemenin hesaplanmasını ister. Bu sadece kayda değer kavisli yüzey içermeyen özellikler (küre veya elipsoid üzerindeki küçük özellikler veya düzlemdeki herhangi bir özellik gibi) için mantıklıdır . Düzlemde çalışırken, tüm sektörleri aynı yönde yönlendirmek de anlamlıdır. (Ancak yangının rüzgar tarafından yayılmasını modelliyor olsaydık, sektörlerin rüzgarla yönlendirilmesini isterdik ve boyutları rüzgar hızına göre değişebilir: verdiğim dilatasyonun genel tanımı için bir motivasyon. (Elipsoid gibi kavisli yüzeylerde genel olarak tüm sektörleri "aynı" yönde yönlendirmek imkansızdır.)
Aşağıdaki durumlarda dilatasyonun hesaplanması nispeten kolaydır:
Özellik düzlemdedir (yani, özelliğin bir haritasını genişletiyoruz ve umarım harita oldukça doğrudur).
Dilatasyon sabit olacaktır : özelliğin her noktasına yayılma, aynı yönelime sahip uygun mahallelerde gerçekleşecektir.
Bu ortak mahalle dışbükeydir. Konveksite hesaplamaları büyük ölçüde basitleştirir ve hızlandırır.
Bu soru bu gibi özel durumlarda uymaktadır: keyfi çokgenlerin kökenleri (geldikleri disklerin merkezleri) temel noktalarda bulunan dairesel sektörler tarafından genişletilmesini istemektedir. Bu sektörlerin 180 dereceden fazla olmaması koşuluyla dışbükey olacaktır. (Daha büyük sektörler her zaman ikiye dışbükey sektörlere bölünebilir; daha küçük iki genişlemenin birleşmesi istenen sonucu verecektir.)
uygulama
Öklid hesaplamaları yaptığımız için - düzlemdeki yayılımı yapıyoruz - bir noktayı sadece dilatasyon mahallesini o noktaya çevirerek genişletebiliriz. (Bunu yapabilmek için mahallenin bir kökene ihtiyacı vartemel noktaya karşılık gelecektir. Örneğin, bu sorudaki sektörlerin kökeni, oluşturuldukları çemberin merkezidir. Bu köken sektörün sınırları üzerindedir. Standart CBS tamponlama operasyonunda mahalle, merkezinde kökeni olan bir dairedir; şimdi kökeni dairenin iç kısmında yatıyor. Bir kökeni seçmek hesaplama açısından çok önemli değildir, çünkü kökeni değiştirmek sadece tüm genişlemeyi kaydırır, ancak doğal olayları modellemek açısından büyük bir sorun olabilir. Aşağıdaki sector
koddaki işlev, bir kaynağın nasıl belirtilebileceğini gösterir.)
Bir çizgi segmentini genişletmek zor olabilir, ancak dışbükey bir mahalle için, dikkatlice seçilmiş bir paralelkenarla birlikte iki uç noktanın genişlemesinin birleşimi olarak genişlemeyi oluşturabiliriz. (Mekânın ilgisi için böyle matematiksel iddiaları kanıtlamak için duraklamayacağım, ancak okuyucuyu kendi kanıtlarını denemeye teşvik edeceğim, çünkü bu anlayışlı bir alıştırmadır.) İşte üç sektör (pembe olarak gösterilen) kullanan bir örnek. Birim yarıçapları vardır ve açıları başlıklarda verilmiştir. Çizgi parçasının kendisi 2 uzunluğuna sahiptir, yataydır ve siyah olarak gösterilir:
Paralelkenar, segmentten mümkün olduğunca uzak olan pembe noktaların yalnızca dikey yönde konumlandırılmasıyla bulunur . Bu, segmente paralel çizgiler boyunca iki alt nokta ve iki üst nokta verir. Sadece dört noktayı bir paralelkenar (mavi ile gösterilen) olarak birleştirmeliyiz. Sağda, sektörün kendisi sadece bir çizgi segmenti olsa bile (gerçek bir çokgen değil) bunun nasıl anlamlı olduğuna dikkat edin: orada, segmentteki her nokta bir mesafe aralığı için kuzeyde 171 derece doğuya çevrildi. Bu uç noktaların kümesi gösterilen paralelkenardır. Bu hesaplamanın ayrıntıları aşağıdaki kodda buffer
tanımlanan işlevde görünür dilate.edges
.
Bir çoklu hattı genişletmek için , onu oluşturan noktaların ve segmentlerin genişlemelerinin birleşimini oluştururuz. Son iki satır dilate.edges
bu döngüyü gerçekleştirir.
Bir çokgenin genişletilmesi için çokgenin iç kısmı ve sınırının genişletilmesi gerekir. (Bu iddia, dilatasyon mahallesi hakkında bazı varsayımlar yapar. Birincisi, tüm mahallelerin çokgenin dilatasyona dahil edilmesini garanti eden (0,0) noktasını içermesidir. Değişken mahallelerde, herhangi bir iç mekanın dilatasyonunun da olduğunu varsayar. Çokgenin noktası, dilatasyon sınırlarının ötesine uzanmayacaktır. Bu durum sabit mahalleler için geçerlidir.)
Bunun nasıl çalıştığına dair bazı örneklere bakalım , önce bir nonagon (ayrıntıyı ortaya çıkarmak için seçildi) ve daha sonra bir daire (sorudaki şekle uydurmak için seçildi). Örnekler aynı üç mahalleyi kullanmaya devam edecek, ancak 1/3 yarıçapına küçülecektir.
Bu şekilde çokgenin içi gri, nokta genişlemeleri (sektörler) pembe ve kenar genişlikleri (paralelkenarlar) mavidir.
"Daire" gerçekten sadece 60-gon, ama bir daireye güzel bir şekilde yaklaşıyor.
Verim
Temel özellik N noktaları ve dilatasyon komşuları M noktaları ile temsil edildiğinde, bu algoritma O ( NM) çabası gerektirir. Bunu, sendikadaki O (N M log (N M)) çaba gerektirebilecek köşelerin ve kenarların karmaşasını basitleştirerek takip etmeliyiz: bu, CBS'den yapmasını isteyen bir şeydir; bunu programlamamız gerekmiyor.
Hesaplama çabası, dışbükey temel özellikler için O (M + N) olarak geliştirilebilir (çünkü orijinal iki şeklin sınırlarını tanımlayan köşe listelerini uygun bir şekilde birleştirerek yeni sınırın etrafında nasıl seyahat edebileceğinizi öğrenebilirsiniz). Bunun daha sonra temizlenmesi gerekmez.
Taban özelliği etrafında ilerledikçe dilatasyon mahallesi yavaşça boyut ve / veya yön değiştirdiğinde, kenarın dilatasyonu, uç noktalarındaki dilatasyonların birleşiminin dışbükey gövdesinden yakın bir şekilde tahmin edilebilir. İki dilatasyon mahallesinde M1 ve M2 noktaları varsa, bu, Shamos & Preparata, Hesaplamalı Geometri'de açıklanan bir algoritma kullanılarak O (M1 + M2) çabasıyla bulunabilir . Bu nedenle, K = M1 + M2 + ... + M (N), N dilatasyon mahallelerindeki toplam köşe sayısı sayısı olarak, dilatasyonu O (K * log (K)) zamanında hesaplayabiliriz.
İstediğimiz tek şey basit bir tamponsa neden böyle bir genellemeyle uğraşmak istiyoruz? Dünyadaki büyük özellikler için, gerçekte sabit boyuta sahip bir dilatasyon mahallesi (disk gibi), bu hesaplamaların yapıldığı haritada değişen boyutlara sahip olabilir. Böylece Öklid geometrisinin tüm avantajlarından yararlanmaya devam ederken elipsoid için doğru hesaplamalar yapmanın bir yolunu elde ederiz .
kod
Örnekler, R
en sevdiğiniz dile (Python, C ++, vb.) Kolayca taşınabilen bu prototiple üretilmiştir . Yapıda, bu cevapta rapor edilen analize paraleldir ve bu nedenle ayrı bir açıklamaya gerek yoktur. Yorumlar bazı ayrıntıları açıklar.
(Trigonometrik hesaplamaların yalnızca normal çokgenler olan örnek özellikleri ve sektörleri oluşturmak için kullanıldığını belirtmek ilginç olabilir . Dilatasyon hesaplamalarının hiçbir kısmı herhangi bir trigonometri gerektirmez.)
#
# 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")
})
Bu örnek (N = 60 ve M = 121 (sol), M = 181 (orta) ve M = 2 (sağ)) için hesaplama süresi dörtte bir saniyeydi. Ancak, bunların çoğu ekran içindi. Tipik olarak, bu R
kod yaklaşık N M = 1,5 milyon / saniye işlemektedir (gösterilen tüm örnek hesaplamaları yapmak için sadece 0,002 saniye sürer). Bununla birlikte, MN ürününün görünümü, ayrıntılı bir mahalle aracılığıyla birçok figürün veya karmaşık figürlerin genişlemesini önemli ölçüde zaman alabilir, bu nedenle dikkat edin! Büyük bir sorunla başa çıkmadan önce daha küçük sorunların zamanlamasını kıyaslayın. Bu gibi durumlarda, raster tabanlı bir çözüme bakılabilir ( uygulanması çok daha kolaydır, esasen tek bir mahalle hesaplaması gerektirir).