如何生成包含最少n个点的不规则网格?


20

给定大量(约100万个)不均匀分布点的样本-是否可以生成包含指定最小n点数量的不规则网格(大小,但形状是否可能不规则?)?

对于我来说,如果这样的网格的“细胞”精确地包含n个点或至少n个点,那么对我来说就不那么重要了。

我知道诸如ArcGIS中的genvecgrid或QGIS / mmgis中的Create Grid Layer之类的解决方案,但是它们都会创建常规网格,从而导致输出的单元格为空(较小的问题-我可以简单地丢弃它们)或具有点数的单元格小于n(更大的问题,因为我需要一种解决方案来聚合这些单元格,可能使用此处的一些工具?)。

我一直闲逛无济于事,并且对商业(ArcGIS和扩展)或免费(Python,PostGIS,R)解决方案都开放。


1
网格需要如何“常规”?我想知道您是否可以进行一些分层聚类,然后仅剪切树状图来满足您的需求(尽管这可能会扩展定义为常规空间配置的内容)。CrimeStat文档提供了此类群集的一些很好的示例。
安迪W

5
您能否确切解释“不规则网格”的含义?这听起来很矛盾:-)。更重要的是,此练习的目的是什么?还要注意,可能还需要其他条件或约束:毕竟,如果您在所有100万个点周围绘制一个正方形,则可以将其视为网格的一部分,并且将包含n个以上的正方形。不过,您可能并不在乎这个琐碎的解决方案:但是,为什么不呢?
ub

@AndyW谢谢。好主意,值得探索。会看看。“网格”的大小和形状对我来说是次要的-优先级(由于数据隐私)是“隐藏” n个特征的后面
radek 2012年

@whuber也谢谢。我同意-但不确定如何命名该分区。如上所述,我的主要动机是数据隐私。有五个点的位置(我无法在最终地图上显示),我想通过覆盖它们的区域来表示它们;并获得均值/中位数/等。价值。我同意可以绘制一个代表它们的矩形或凸包-我猜这将是最终的数据隐私保护吗?;]但是-用形状边界表示它会更有用,比如说10个要素。然后-我仍然可以保留空间模式。
radek

1
IMO根据您的描述,我将使用某种类型的插值并显示一个光栅图(也许最小N的大小的自适应带宽足以使数据平滑)。就CrimeStat而言,我使用的最大文件约为我相信的100,000个案例(并且聚类肯定会随着时间的流逝)。您可能需要对数据进行一些预概括,以将其表示为更少的案例,并且无论您想要什么,仍然可以获得理想的结果。这是一个非常简单的程序,我建议您花几分钟时间尝试一下,然后亲自看看。
安迪W

Answers:


26

我看到MerseyViking建议使用四叉树。我将提出相同的建议,并且为了解释它,这是代码和示例。该代码是用书面语言编写的,R但应该易于移植到Python。

这个想法非常简单:在x方向上将点分成大约一半,然后在y方向上递归地将两半分开,在每个级别上交替方向,直到不再需要拆分为止。

因为目的是掩盖实际的点位置,所以在分割中引入一些随机性很有用。一种快速简单的方法是在分位数集上将少量随机数从50%分开。以这种方式(a)分裂值极不可能与数据坐标重合,因此点将唯一地落入由分区创建的象限中,并且(b)点坐标将无法从四叉树精确地重建。

因为目的是k在每个四叉树叶子中维持最少数量的节点,所以我们实现了四叉树的受限形式。它将支持(1)将聚类点分为组,每个组之间有k2 * k-1个元素,以及(2)映射象限。

这段R代码创建了节点和末端叶子的树,并按类对其进行区分。类标签加快了后处理(例如绘图)的速度,如下所示。该代码使用数字值作为ID。这在树中的深度达到52(使用双精度;如果使用无符号长整数,则最大深度为32)。对于较深的树(在任何应用程序中都不太可能,因为至少k涉及* 2 ^ 52点),所以id必须是字符串。

quadtree <- function(xy, k=1) {
  d = dim(xy)[2]
  quad <- function(xy, i, id=1) {
    if (length(xy) < 2*k*d) {
      rv = list(id=id, value=xy)
      class(rv) <- "quadtree.leaf"
    }
    else {
      q0 <- (1 + runif(1,min=-1/2,max=1/2)/dim(xy)[1])/2 # Random quantile near the median
      x0 <- quantile(xy[,i], q0)
      j <- i %% d + 1 # (Works for octrees, too...)
      rv <- list(index=i, threshold=x0, 
                 lower=quad(xy[xy[,i] <= x0, ], j, id*2), 
                 upper=quad(xy[xy[,i] > x0, ], j, id*2+1))
      class(rv) <- "quadtree"
    }
    return(rv)
  }
  quad(xy, 1)
}

注意,该算法的递归分而治之设计(和,因此,大部分的后处理算法)的装置,该时间要求是O(m)和RAM使用是O(n),其中m是的数单元格和n是点数。 mn除以每个单元格的最小点成比例,k。这对于估计计算时间很有用。例如,如果将n = 10 ^ 6点划分为50-99点(k = 50)的单元需要13秒,则m = 10 ^ 6/50 =20000。如果您想将其划分为5-9每个像元的点数(k = 5),m大10倍,因此计时时间增加到大约130秒。(因为随着像元变小,在它们的中心附近分解一组坐标的过程变得更快,所以实际时间仅为90秒。)要一直到每个像元k = 1点,将需要大约六倍的时间。还是九分钟,我们可以预期代码实际上会比这快一点。

在继续之前,让我们生成一些有趣的不规则空间数据并创建其受限的四叉树(经过0.29秒的时间):

四叉树

这是生成这些图的代码。它利用了R多态性:例如,points.quadtree只要将points函数应用于quadtree对象,就会被调用。该功能的强大之处在于,可以根据聚类标识符为这些点着色,从而极大地简化了功能:

points.quadtree <- function(q, ...) {
  points(q$lower, ...); points(q$upper, ...)
}
points.quadtree.leaf <- function(q, ...) {
  points(q$value, col=hsv(q$id), ...)
}

绘制网格本身有点棘手,因为它需要重复裁剪用于四叉树划分的阈值,但是相同的递归方法既简单又优雅。如果需要,可以使用变体构造象限的多边形表示。

lines.quadtree <- function(q, xylim, ...) {
  i <- q$index
  j <- 3 - q$index
  clip <- function(xylim.clip, i, upper) {
    if (upper) xylim.clip[1, i] <- max(q$threshold, xylim.clip[1,i]) else 
      xylim.clip[2,i] <- min(q$threshold, xylim.clip[2,i])
    xylim.clip
  } 
  if(q$threshold > xylim[1,i]) lines(q$lower, clip(xylim, i, FALSE), ...)
  if(q$threshold < xylim[2,i]) lines(q$upper, clip(xylim, i, TRUE), ...)
  xlim <- xylim[, j]
  xy <- cbind(c(q$threshold, q$threshold), xlim)
  lines(xy[, order(i:j)],  ...)
}
lines.quadtree.leaf <- function(q, xylim, ...) {} # Nothing to do at leaves!

再举一个例子,我生成了1,000,000个点,并将它们分为5-9个组。时间是91.7秒。

n <- 25000       # Points per cluster
n.centers <- 40  # Number of cluster centers
sd <- 1/2        # Standard deviation of each cluster
set.seed(17)
centers <- matrix(runif(n.centers*2, min=c(-90, 30), max=c(-75, 40)), ncol=2, byrow=TRUE)
xy <- matrix(apply(centers, 1, function(x) rnorm(n*2, mean=x, sd=sd)), ncol=2, byrow=TRUE)
k <- 5
system.time(qt <- quadtree(xy, k))
#
# Set up to map the full extent of the quadtree.
#
xylim <- cbind(x=c(min(xy[,1]), max(xy[,1])), y=c(min(xy[,2]), max(xy[,2])))
plot(xylim, type="n", xlab="x", ylab="y", main="Quadtree")
#
# This is all the code needed for the plot!
#
lines(qt, xylim, col="Gray")
points(qt, pch=".")

在此处输入图片说明


作为如何与GIS交互的示例,让我们使用该shapefiles库将所有四叉树单元写为多边形shapefile 。该代码模拟的裁剪例程lines.quadtree,但是这一次它必须生成单元的向量描述。这些作为数据帧输出,供shapefiles库使用。

cell <- function(q, xylim, ...) {
  if (class(q)=="quadtree") f <- cell.quadtree else f <- cell.quadtree.leaf
  f(q, xylim, ...)
}
cell.quadtree <- function(q, xylim, ...) {
  i <- q$index
  j <- 3 - q$index
  clip <- function(xylim.clip, i, upper) {
    if (upper) xylim.clip[1, i] <- max(q$threshold, xylim.clip[1,i]) else 
      xylim.clip[2,i] <- min(q$threshold, xylim.clip[2,i])
    xylim.clip
  } 
  d <- data.frame(id=NULL, x=NULL, y=NULL)
  if(q$threshold > xylim[1,i]) d <- cell(q$lower, clip(xylim, i, FALSE), ...)
  if(q$threshold < xylim[2,i]) d <- rbind(d, cell(q$upper, clip(xylim, i, TRUE), ...))
  d
}
cell.quadtree.leaf <- function(q, xylim) {
  data.frame(id = q$id, 
             x = c(xylim[1,1], xylim[2,1], xylim[2,1], xylim[1,1], xylim[1,1]),
             y = c(xylim[1,2], xylim[1,2], xylim[2,2], xylim[2,2], xylim[1,2]))
}

点本身可以​​使用read.shp或通过导入(x,y)坐标的数据文件直接读取。

使用示例:

qt <- quadtree(xy, k)
xylim <- cbind(x=c(min(xy[,1]), max(xy[,1])), y=c(min(xy[,2]), max(xy[,2])))
polys <- cell(qt, xylim)
polys.attr <- data.frame(id=unique(polys$id))
library(shapefiles)
polys.shapefile <- convert.to.shapefile(polys, polys.attr, "id", 5)
write.shapefile(polys.shapefile, "f:/temp/quadtree", arcgis=TRUE)

(在xylim此处使用任何所需的范围可以进入子区域或将映射扩展到更大的区域;此代码默认为点的范围。)

仅此一项就足够了:这些多边形到原始点的空间连接将识别出聚类。一旦确定,数据库“摘要”操作将生成每个像元内各个点的摘要统计信息。


哇!太棒了 一旦回到办公室,就可以使用我的数据进行
尝试

4
最佳答案@whuber!+1
MerseyViking

1
(1)您可以使用(尤其是shapefiles软件包直接读取shapefile,也可以导出ASCII文本中的(x,y)坐标并使用读取它们read.table。(2)我建议qt以两种形式写出:第一,作为点shapefile,xy其中将id字段作为簇标识符包括在内;第二,将绘制的线段lines.quadtree写为折线shapefile(或类似处理将单元格写为多边形shapefile)。这很简单,只要修改lines.quadtree.leaf,以输出xylim为矩形。(请参阅编辑。)
whuber

1
@whubber非常感谢您的更新。一切顺利。当之无愧的+50,尽管现在我认为它值得+500!
radek 2012年

1
我怀疑计算出的ID出于某些原因不是唯一的。在以下定义中进行以下更改quad:(1)初始化id=1;(2)变化id/2id*2lower=线; (3)id*2+1upper=行进行类似的更改。(我将编辑我的答复以反映这一点。)这还应该考虑面积的计算:根据您的GIS,所有面积都是正值或全部都是负值。如果它们均为负数,则反转xy中的列表cell.quadtree.leaf
ub

6

查看此算法是否为您的数据样本提供了足够的匿名性:

  1. 从常规网格开始
  2. 如果多边形的阈值小于阈值,请与顺时针旋转的相邻交替元素(E,S,W,N)合并。
  3. 如果多边形小于阈值,则转到2,否则转到下一个多边形

例如,如果最小阈值为3:

算法


1
细节在于魔鬼:看来这种方法(或几乎任何聚集性聚类算法)都可能在整个地方留下很少的“孤立”点,从而无法进行处理。我并不是说这种方法是不可能的,但是如果没有实际算法及其在实际点数据集中的应用示例,我将保持健康的怀疑态度。
ub

确实,这种方法可能有问题。我正在考虑的这种方法的一个应用是使用点作为住宅建筑物的表示。我认为这种方法在人口更稠密的地区效果很好。但是,仍然存在这样的情况:“在无处可走的地方”实际上只有一两个建筑物,这将需要大量的迭代,并且会导致很大的面积最终达到最小阈值。
radek 2012年

5

与Paulo有趣的解决方案类似,如何使用四叉树细分算法?

设置您想要四叉树去的深度。您还可以为每个像元设置最小或最大点数,因此某些节点比其他节点更深/更小。

细分您的世界,丢弃空节点。冲洗并重复直到达到标准。


谢谢。您会为此推荐什么软件?
radek 2012年

1
原则上,这是一个好主意。但是,如果您永远不允许每个单元格的最小点数少于正数,那么空节点将如何出现?(四叉树种类繁多,因此,空节点的可能性表明您正在考虑一个不适合数据的四叉树,这引起了人们对它对预期任务的用处的担忧。)
whuber

1
我这样描述:想象一个节点中的点数超过最大阈值,但是它们聚集在该节点的左上角。该节点将被细分,但是右下子节点将为空,因此可以对其进行修剪。
MerseyViking,2012年

1
我知道您在做什么(+1)。诀窍是在由坐标确定的点(例如它们的中位数)进行细分,从而保证没有空单元格。否则,四叉树主要由点所占据的空间决定,而不是点本身。然后,您的方法将成为执行@Paulo提出的通用想法的有效方法。
Whuber

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.