如何测量SpatialLines对象的相似性


9

SpatialLines在R:中创建了两个对象数字

这些对象是通过以下方式创建的:

library(sp)
xy <- cbind(x,y)
xy.sp = sp::SpatialPoints(xy)
spl1 <- sp::SpatialLines(list(Lines(Line(xy.sp), ID="a")))

现在,我想以某种方式得出结论,这是旋转和翻转的同一条线,并且它们之间的差等于0(即形状相等)。

为此,可以使用maptools包装并旋转第1行,例如:

spl180 <- maptools::elide(spl1, rotate=180)

然后必须使用rgeos包装将每条旋转的线与2号线进行比较,例如:

hdist <- rgeos::gDistance(spl180, spl2, byid=FALSE, hausdorff=TRUE)

但是,这是计算SpatialLines对象匹配的昂贵方法,尤其是在对象数约为1000的情况下。

有什么聪明的方法可以完成这项工作吗?

PS此外,上述方法不能保证所有可能的旋转和翻转。

P.S2。如果#1线相对于#2缩小,则#1和#2之间的差必须仍等于0。

更新:

在此处输入图片说明

Answers:


9

任何真正通用的有效方法都会标准化形状的表示,以使形状不会随着内部表示的旋转,平移,反射或微不足道的变化而改变。

一种方法是从一端开始,将每个连接的形状列出为边长和(有符号)角的交替序列。(在没有零长度的边缘或直角的意义上,形状应该是“干净的”。)要使该常数在反射下不变,如果第一个非零角为负,则将所有角取反。

(由于任何连接的n个顶点的折线将具有n -1个以n -2角分隔的边,我发现在R下面的代码中使用由两个数组组成的数据结构比较方便,一个数组用于边长,另一个数组用于边长$lengths。角度$angles。线段将没有角度在所有的,所以它来处理零长度数组在这样的数据结构是很重要的。)

这样的表示可以按字典顺序排序。 应该对在标准化过程中累积的浮点错误留有余地。一个优雅的过程将根据原始坐标来估计这些误差。在下面的解决方案中,使用了一种更简单的方法,其中两个长度在相对基础上相差很小时,被视为相等 角度在绝对基础上可能仅相差很小。

要使它们在基本方向的反转下保持不变,请在折线的折线及其反转之间选择词典上最早的表示形式。

要处理多部分折线,请按字典顺序排列其组成部分。

为了找到欧几里得变换下的等价类

  • 创建形状的标准化表示。

  • 对标准表示法进行字典编排。

  • 遍历排序顺序以标识相等表示的序列。

计算时间与O(n * log(n)* N)成比例,其中n是要素数量,N是任何要素中最大的顶点数量。 这是有效的。

值得一提的是,基于容易计算的不变几何属性(例如折线长度,中心和围绕该中心的弯矩)的初步分组通常可用于简化整个过程。只需在每个这样的初步组中找到全等特征的子组。对于形状将非常需要此处给出的完整方法,否则形状将非常相似,以至于这种简单的不变式仍然无法区分它们。例如,由栅格数据构造的简单要素可能具有此类特征。但是,由于这里给出的解决方案仍然非常有效,因此如果要努力实现它,那么它本身就可以很好地工作。


左图显示了五条折线,外加15条折线,这些折线是通过随机平移,旋转,反射和内部方向反转(不可见)获得的。右图根据欧几里得当量类别为它们着色:所有相同颜色的图都相同。不同的颜色不一致。

数字

R代码如下。 当输入更新为500个形状,500个额外的(全等)形状(每个形状平均100个顶点)时,此机器上的执行时间为3秒。

这段代码是不完整的:因为R没有本地的字典排序,而且我不希望自己从头开始编码,所以我只是对每个标准化形状的第一个坐标进行排序。这对于此处创建的随机形状将是很好的,但是对于生产工作,应实施完整的词典编目排序。该功能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)))

当一个人在变换组中包含任意扩张(或“等价物”)时,等价类就是仿射几何的全等类。这种复杂性很容易处理:例如,将所有折线标准化为具有总单位长度。
whuber

非常感谢。只是一个问题:形状应表示为SpatialLines还是SpatialPolygons?
Klausos Klausos 2015年

多边形会带来另一种复杂情况:其边界没有明确的终点。有很多方法可以解决该问题,例如标准化表示形式,使其始于(例如)首先按xy字典顺序排序的顶点,然后围绕多边形沿逆时针方向进行。(在拓扑上是“干净的”连接的多边形将只有一个这样的顶点。)将形状视为多边形还是折线取决于它所代表的特征类型:没有固有的方式可以说出任何封闭的点列表,无论是意为折线或多边形。
ub

抱歉,有一个简单的问题,但我应该请它理解您的示例。您的对象shapes.std同时具有$ lengths和$ angles。但是,如果我在xy数据上运行此代码(例如[1,] 3093.5 -2987.8 [2,] 3072.7 -2991.0等),则它不会估计角度,也不会绘制形状。如果运行plot(shapes [[1]]),则可以清楚地看到我的折线。因此,我应该如何在R中保存折线以能够在数据上测试您的代码?
Klausos Klausos 2015年

我从与您相同的数据结构开始:(x,y)坐标数组。我的数组将这些坐标放入列中(就像您曾经使用rbind(x,y)而不是一样cbind(x,y))。这就是您所需要的:sp不使用该库。如果你想跟着什么做了详细的,我建议你在开始时,也就是说,n.shapes <- 2n.shapes.new <- 3,和p.mean <- 1。然后shapesshapes.std等等都足够小,易于检查。处理所有这些问题的一种优雅且“正确”的方式将是创建一标准化的特征表示。
whuber

1

您要随心所欲地旋转和扩张很多!不知道Hausdorff距离在那儿有多有用,但请检查一下。我的方法是减少通过廉价数据检查案件的数量。例如,如果两个线串的长度不是整数比(假设使用整数/分度标度),则可以跳过昂贵的比较。您可以类似地检查边界框区域或它们的凸包区域是否比例合适。我确信您可以针对质心进行大量廉价的检查,例如距起点/终点的距离或角度。

只有这样,如果您检测到缩放,请撤消缩放并进行真正昂贵的检查。

澄清:我不知道您使用的软件包。整数比是指您应该将两个距离相除,检查结果是否为整数,如果不是,则将该值取反(可能是您选择的顺序错误),然后重新检查。如果得到整数或足够接近,则可以推断出可能正在进行缩放。或者可能只是两个不同的形状。

至于边界框,您可能会得到代表它的矩形的相对点,因此从它们中取出面积是简单的算法。比率比较背后的原理是相同的,只是结果将被平方。如果您不能很好地将它们从R程序包中删除,请不要打扰凸包,这只是一个主意(可能还不够便宜)。


非常感谢。您能否解释一下如何检测两个线串的长度是否不是整数比?另外,如果您能举一个例子来检查“边界框区域或凸包区域的比例
是否合适

例如,如果我从空间数据中提取空间边界框,那么我只会收到两点:spl <-sp :: SpatialLines(list(Lines(Line(Line(xy.sp),ID = i)))b <-bbox( spl)
Klausos Klausos 2015年

延长了主要职务。
lynxlynxlynx

“如果得到一个整数或足够接近,则可以推断出可能正在进行缩放。” 用户不能应用1.4左右的比例吗?
赫尔曼·卡里略

可以,但是我的假设很明确,尤其是在以后的编辑中。我在想象Webmap样式的缩放,其中一个很好的限制。
lynxlynxlynx

1

比较这些折线的一个好方法是依赖于表示形式为每个顶点的(距离,转弯角度)序列:对于由点组成的线P1, P2, ..., PN,该序列为:

(距离(P1P2),角度(P1,P2,P3),距离(P2P3),...,角度(P(N-2),P(N-1),PN),距离(P(N-1) )PN))。

根据您的要求,当且仅当两条线的相应顺序相同(以顺序和角度方向为模)时,两条线才相等。比较数字序列是微不足道的。

通过仅计算每个折线序列一次,并且如lynxlynxlynx所建议的,仅对具有相同琐碎特征(长度,顶点数...)的折线测试序列相似性,计算应该真正快速!


这是正确的想法。但是,要使其真正起作用,需要解决许多细节,例如应对反射,内部方向,多个连接组件的可能性以及浮点舍入误差。在我提供的解决方案中对它们进行了讨论。
ub

是的,我只描述了主要思想。您的答案非常完整(就像通常:
julien 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.