Setiap metode efektif yang bertujuan umum akan membakukan representasi bentuk sehingga tidak akan berubah saat rotasi, terjemahan, refleksi, atau perubahan sepele dalam representasi internal.
Salah satu cara untuk melakukan ini adalah mendaftar setiap bentuk yang terhubung sebagai urutan bolak panjang tepi dan (ditandatangani) sudut, mulai dari satu ujung. (Bentuknya harus "bersih" dalam arti tidak memiliki tepi panjang nol atau sudut lurus.) Untuk membuat invarian ini dalam refleksi, meniadakan semua sudut jika yang bukan nol pertama negatif.
(Karena setiap polyline terhubung dari n simpul akan memiliki n -1 tepi dipisahkan oleh sudut n -2, saya merasa nyaman dalam R
kode di bawah ini untuk menggunakan struktur data yang terdiri dari dua array, satu untuk panjang tepi $lengths
dan yang lainnya untuk sudut, .Segmen $angles
garis tidak akan memiliki sudut sama sekali, jadi penting untuk menangani array panjang nol dalam struktur data seperti itu.)
Representasi seperti itu dapat dipesan secara leksikografis. Beberapa kelonggaran harus dibuat untuk kesalahan floating-point yang diakumulasikan selama proses standardisasi. Prosedur yang elegan akan memperkirakan kesalahan tersebut sebagai fungsi dari koordinat asli. Dalam solusi di bawah ini, metode yang lebih sederhana digunakan di mana dua panjang dianggap sama ketika mereka berbeda dengan jumlah yang sangat kecil pada basis relatif. Sudut dapat berbeda hanya dengan jumlah yang sangat kecil pada basis absolut.
Untuk membuatnya tidak berubah di bawah pembalikan orientasi yang mendasarinya, pilih representasi paling awal secara leksikografis antara polyline dan pembalikannya.
Untuk menangani multi-bagian polyline, atur komponennya dalam urutan leksikografis.
Untuk menemukan kelas kesetaraan di bawah transformasi Euclidean, maka,
Buat representasi standar dari bentuk.
Lakukan semacam leksikografis dari representasi standar.
Buat melewati urutan diurutkan untuk mengidentifikasi urutan representasi yang sama.
Waktu komputasi sebanding dengan O (n * log (n) * N) di mana n adalah jumlah fitur dan N adalah jumlah simpul terbesar dalam fitur apa pun. Ini efisien.
Mungkin perlu disebutkan secara sepintas bahwa pengelompokan pendahuluan berdasarkan sifat geometri invarian yang mudah dihitung, seperti panjang polyline, pusat , dan momen tentang pusat itu, sering dapat diterapkan untuk merampingkan seluruh proses. Satu hanya perlu menemukan subkelompok fitur kongruen dalam masing-masing kelompok awal tersebut. Metode lengkap yang diberikan di sini akan diperlukan untuk bentuk yang sebaliknya akan sangat mirip sehingga invarian sederhana seperti itu masih tidak akan membedakannya. Fitur sederhana yang dibangun dari data raster mungkin memiliki karakteristik seperti itu, misalnya. Namun, karena solusi yang diberikan di sini sangat efisien, sehingga jika seseorang akan pergi ke upaya mengimplementasikannya, mungkin akan bekerja dengan baik dengan sendirinya.
Contoh
Gambar sebelah kiri menunjukkan lima polyline ditambah 15 yang diperoleh dari yang melalui terjemahan acak, rotasi, refleksi, dan pembalikan orientasi internal (yang tidak terlihat). Figur tangan kanan mewarnai mereka sesuai dengan kelas ekivalensi Euclidean mereka: semua figur dengan warna yang sama adalah kongruen; warna yang berbeda tidak kongruen.
R
kode berikut. Ketika input diperbarui ke 500 bentuk, 500 bentuk ekstra (kongruen), dengan rata-rata 100 simpul per bentuk, waktu eksekusi pada mesin ini adalah 3 detik.
Kode ini tidak lengkap: karena R
tidak memiliki jenis leksikografi asli, dan saya tidak merasa ingin mengkodekan satu dari awal, saya hanya melakukan penyortiran pada koordinat pertama dari setiap bentuk standar. Itu akan baik-baik saja untuk bentuk acak yang dibuat di sini, tetapi untuk pekerjaan produksi, jenis leksikografis lengkap harus diterapkan. Fungsi order.shape
akan menjadi satu-satunya yang terpengaruh oleh perubahan ini. Inputnya adalah daftar bentuk standar s
dan outputnya adalah urutan indeks ke dalam s
yang akan mengurutkannya.
#
# Create random shapes.
#
n.shapes <- 5 # Unique shapes, up to congruence
n.shapes.new <- 15 # Additional congruent shapes to generate
p.mean <- 5 # Expected number of vertices per shape
set.seed(17) # Create a reproducible starting point
shape.random <- function(n) matrix(rnorm(2*n), nrow=2, ncol=n)
shapes <- lapply(2+rpois(n.shapes, p.mean-2), shape.random)
#
# Randomly move them around.
#
move.random <- function(xy) {
a <- runif(1, 0, 2*pi)
reflection <- sign(runif(1, -1, 1))
translation <- runif(2, -8, 8)
m <- matrix(c(cos(a), sin(a), -sin(a), cos(a)), 2, 2) %*%
matrix(c(reflection, 0, 0, 1), 2, 2)
m <- m %*% xy + translation
if (runif(1, -1, 0) < 0) m <- m[ ,dim(m)[2]:1]
return (m)
}
i <- sample(length(shapes), n.shapes.new, replace=TRUE)
shapes <- c(shapes, lapply(i, function(j) move.random(shapes[[j]])))
#
# Plot the shapes.
#
range.shapes <- c(min(sapply(shapes, min)), max(sapply(shapes, max)))
palette(gray.colors(length(shapes)))
par(mfrow=c(1,2))
plot(range.shapes, range.shapes, type="n",asp=1, bty="n", xlab="", ylab="")
invisible(lapply(1:length(shapes), function(i) lines(t(shapes[[i]]), col=i, lwd=2)))
#
# Standardize the shape description.
#
standardize <- function(xy) {
n <- dim(xy)[2]
vectors <- xy[ ,-1, drop=FALSE] - xy[ ,-n, drop=FALSE]
lengths <- sqrt(colSums(vectors^2))
if (which.min(lengths - rev(lengths))*2 < n) {
lengths <- rev(lengths)
vectors <- vectors[, (n-1):1]
}
if (n > 2) {
vectors <- vectors / rbind(lengths, lengths)
perps <- rbind(-vectors[2, ], vectors[1, ])
angles <- sapply(1:(n-2), function(i) {
cosine <- sum(vectors[, i+1] * vectors[, i])
sine <- sum(perps[, i+1] * vectors[, i])
atan2(sine, cosine)
})
i <- min(which(angles != 0))
angles <- sign(angles[i]) * angles
} else angles <- numeric(0)
list(lengths=lengths, angles=angles)
}
shapes.std <- lapply(shapes, standardize)
#
# Sort lexicographically. (Not implemented: see the text.)
#
order.shape <- function(s) {
order(sapply(s, function(s) s$lengths[1]))
}
i <- order.shape(shapes.std)
#
# Group.
#
equal.shape <- function(s.0, s.1) {
same.length <- function(a,b) abs(a-b) <= (a+b) * 1e-8
same.angle <- function(a,b) min(abs(a-b), abs(a-b)-2*pi) < 1e-11
r <- function(u) {
a <- u$angles
if (length(a) > 0) {
a <- rev(u$angles)
i <- min(which(a != 0))
a <- sign(a[i]) * a
}
list(lengths=rev(u$lengths), angles=a)
}
e <- function(u, v) {
if (length(u$lengths) != length(v$lengths)) return (FALSE)
all(mapply(same.length, u$lengths, v$lengths)) &&
all(mapply(same.angle, u$angles, v$angles))
}
e(s.0, s.1) || e(r(s.0), s.1)
}
g <- rep(1, length(shapes.std))
for (j in 2:length(i)) {
i.0 <- i[j-1]
i.1 <- i[j]
if (equal.shape(shapes.std[[i.0]], shapes.std[[i.1]]))
g[j] <- g[j-1] else g[j] <- g[j-1]+1
}
palette(rainbow(max(g)))
plot(range.shapes, range.shapes, type="n",asp=1, bty="n", xlab="", ylab="")
invisible(lapply(1:length(i), function(j) lines(t(shapes[[i[j]]]), col=g[j], lwd=2)))