真に汎用的な効果的な方法は、形状の表現を標準化し、内部表現の回転、並進、反射、またはささいな変更によって変更されないようにします。
これを行う1つの方法は、接続された各形状を、一端から開始して、エッジの長さと(符号付き)角度の交互のシーケンスとしてリストすることです。(形状は、長さゼロのエッジまたは直線角度がないという意味で「クリーン」である必要があります。)反射の下でこれを不変にするには、最初のゼロ以外の角度が負の場合、すべての角度を無効にします。
いずれかに接続ポリラインので(n個の頂点を有することになるN -1エッジがで区切られたN -2角度は、私はそれが便利発見したR
二つのアレイ、エッジの長さのための1つからなるデータ構造で使用するために、次のコード$lengths
とのための他のangles、。$angles
ラインセグメントには角度がまったくないため、このようなデータ構造では長さ0の配列を処理することが重要です。
このような表現は、辞書式順序で並べることができます。 標準化プロセス中に蓄積された浮動小数点エラーをある程度考慮する必要があります。エレガントな手順では、これらのエラーを元の座標の関数として推定します。以下のソリューションでは、2つの長さが相対的に非常にわずかに異なる場合に2つの長さが等しいと見なされる、より単純な方法が使用されています。 角度の違いは、絶対的に非常に小さい場合のみです。
基礎となる方向の反転の下でそれらを不変にするには、ポリラインのそれとその反転の間の辞書式に最も早い表現を選択します。
マルチパートポリラインを処理するには、コンポーネントを辞書式順序で配置します。
ユークリッド変換の下で等価クラスを見つけるには、
計算時間はO(n * log(n)* N)に比例します。nはフィーチャの数、Nは任意のフィーチャの頂点の最大数です。 これは効率的です。
おそらく、ポリラインの長さ、中心、その中心に関するモーメントなどの簡単に計算される不変の幾何学的プロパティに基づく予備的なグループ化は、多くの場合、プロセス全体を合理化するために適用できることに言及する価値があります。そのような各予備グループ内で合同な特徴のサブグループを見つけるだけでよい。ここで与えられた完全な方法は、他の点では非常に類似していて、そのような単純な不変式でも区別できないような形状に必要です。たとえば、ラスターデータから構築された単純なフィーチャには、そのような特性があります。ただし、ここで示すソリューションは非常に効率的であるため、それを実装するための努力をすれば、それだけで問題なく機能する可能性があります。
例
左側の図は、5つのポリラインと、ランダムな平行移動、回転、反射、および内部方向の反転(表示されていない)を介して取得された15個のポリラインを示しています。右側の図は、ユークリッド同値類に従ってそれらに色を付けます。共通の色のすべての図は合同です。異なる色は合同ではありません。
R
コードが続きます。 入力が500のシェイプ、500の追加(合同)シェイプ、シェイプごとに平均100頂点に更新された場合、このマシンでの実行時間は3秒でした。
このコードは不完全です:ので、R
ネイティブの辞書の並べ替えを持っていない、と私は単純に最初の各標準化された形状の座標でソートを実行し、ゼロから1を符号化するような気がしませんでした。ここで作成されたランダムな形状についてはそれで問題ありませんが、本番環境では完全な辞書編集ソートを実装する必要があります。order.shape
この変更の影響を受けるのは関数のみです。その入力は標準化された形状のリストでs
あり、その出力はs
それをソートするためのインデックスのシーケンスです。
#
# 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)))