从自定义 txt 地图确定多边形坐标

Determine polygon coordinates from custom txt map

我有一个 .txt 文件,看起来像这样

+----------+--------+------------------+
|          |         \                 |
|          |          \                |
|          |           \               |
|          |            +------+-------+
|          |                   |       |
|          |                   |       |
+----------+-------------------+-------+

如何识别这些多边形?

对于这个例子,我希望识别 4 个多边形。

我可以读取线条并识别每个顶点 ("+") 的坐标(行、列),但我不确定如何获取边坐标。我的主要目标是拥有一组描述每个多边形边界区域的点。

如何在 R 或 Python 中执行此操作?

我并不是说这非常有效,但这至少是解决 R 中问题的一种方法。首先,这是我读入数据的方式。

raw <- r"(
+----------+--------+------------------+
|          |         \                 |
|          |          \                |
|          |           \               |
|          |            +------+-------+
|          |                   |       |
|          |                   |       |
+----------+-------------------+-------+
)"

data <- do.call("rbind", strsplit(readLines(textConnection(raw)), ""))

这将创建一个数组 data,其行数和列数与输入的所有字符都相同。

基本思路是我尝试填充该区域中的每个“空白”区域,并跟踪我们在填充过程中遇到的所有角。所以首先,一个辅助函数来获取给定单元格周围的 rows/columns

allnei <- expand.grid(-1:1, -1:1)
allnei <- allnei[allnei[,1] !=0 | allnei[,2] !=0, ]
get_neighbors <- function(point, data) {
  cand <- t(c(point) +t(allnei))
  cand[
    cand[,1]>=1 & cand[,1]<=nrow(data) & 
    cand[,2]>=1 & cand[,2]<=ncol(data), 
    TRUE
  ]
}

然后大部分工作发生在 fill 函数

fill <- function(point, data, corners=matrix(nrow=0, ncol=2), fill_with="1") {
  if (!is.matrix(point)) {
    point <- matrix(point, ncol=2)
  }
  cand <- get_neighbors(point, data)
  data[point] <- fill_with
  corner_cand <- which(data[cand] == "+")
  if (length(corner_cand)) {
    corners <- unique(rbind(corners, cand[corner_cand, ]))  
  }
  empty_cand <- which(data[cand] == " "  & (cand[,1]==point[,1] | cand[,2]==point[,2]))
  if (length(empty_cand)>0) {
    for(i in empty_cand) {
      result <- fill(cand[i,], data, corners, fill_with)
      data <- result$data
      corners <- unique(rbind(corners, result$corners))
    }
  }
  list(data=data, corners=corners)
}

所以从一个“空”单元格开始,我们寻找相邻的单元格并将它们标记为同一区域。我们递归地执行此操作,直到我们 运行 超出相邻的空单元格。

现在我们需要一个包装器,它会一直这样做直到没有空白区域。

find_regions <- function(data) {
  regions <- list()
  reg <- 1
  empty <- which(data==" ", arr.ind=TRUE)
  while(nrow(empty)>0) {
    empty <- empty[1,]
    results <- fill(empty, data, fill_with=reg)    
    regions <- c(regions, list(results$corners))
    data <- results$data
    reg <- reg+1
    empty <- which(data==" ", arr.ind=TRUE)
  }
  list(data=data, regions=regions)
}
result <- find_regions(data)
result$regions

给出输出

[1]]
     Var1 Var2
[1,]    1    1
[2,]    8    1
[3,]    8   12
[4,]    1   12

[[2]]
     Var1 Var2
[1,]    1   12
[2,]    8   12
[3,]    1   21
[4,]    5   25
[5,]    5   32
[6,]    8   32

[[3]]
     Var1 Var2
[1,]    5   25
[2,]    5   32
[3,]    5   40
[4,]    1   40

[[4]]
     Var1 Var2
[1,]    5   32
[2,]    8   32
[3,]    5   40
[4,]    8   40

多边形顶点的所有 row/column 位置在哪里。我们可以看到

的区域
cat(paste(apply(result$data, 1, paste, collapse=""), collapse="\n"))
# +----------+--------+------------------+
# |1111111111|222222222333333333333333|
# |1111111111|222222222233333333333333|
# |1111111111|222222222223333333333333|
# |1111111111|222222222222+------+-------+
# |1111111111|2222222222222222222|4444444|
# |1111111111|2222222222222222222|4444444|
# +----------+-------------------+-------+

我还考虑过提取连接角的列表。这种方法基本上是寻找所有的角,寻找存在“墙”或连接器的方向,然后找到该方向的第一个角。然后我们跟踪所有发现的对。所以首先,这里有一个帮助函数来找到“连接”的角

find_hit <- function(corner, corners, rvec, cvec) {
  best <- NA
  best_dist <- Inf
  for(i in 1:nrow(corners)) {
    rdist <- corners[i, 1] - corner[,1]
    cdist <- corners[i, 2] - corner[,2]
    if(sign(rdist) == sign(rvec) & sign(cdist) == sign(cvec) & 
      ((rvec ==0 | cvec== 0) | (rdist%/%rvec ==cdist %/%cvec))) {
      dist <- rdist + cdist
      if (dist < best_dist) {
        best_dist <- dist
        best <- i
      }
    }
  }
  best
}

这个想法是,给定一个点和一个候选点列表,我们沿着一个特定的“向量”看,看看在那个方向的尽头有一个点,return 第一个命中。我们还有一个数字助手来确保我们正在查看的点是“在边界内”

in_bounds <- (function(data) {
  function(r, c) {
    r > 0 & r <= nrow(data) & c > 0 & c <= ncol(data)
  }
})(data)

现在我们找到所有的角并循环遍历它们以找到连接

corners <- which(data=="+", arr.ind = TRUE)
edges <- list()
for (i in 1:(nrow(corners)-1)) {
  corner <- corners[i, , drop=FALSE]
  rest <- corners[-(1:i), , drop=FALSE]
  r <- corner[, 1]
  c <- corner[, 2]
  for (dr in c(-1, 0, 1)) {
    for (dc in c(-1, 0, 1)) {
      if (in_bounds(r+dr, c+dc) & (dr != 0| dc != 0) && data[r+dr, c+dc]!=" ") {
        hit <- find_hit(corner, rest, dr, dc)
        if (!is.na(hit)) {
          edges <- c(edges, list(c(i, i+hit)))
        } 
      }
    }
  }
}
edges

所以edges是一个列表,告诉我们哪些角是相连的。我们可以用

把它变成一个 igraph 对象
library(igraph)
dd <- as.data.frame(do.call(rbind, edges))
dd[] <- lapply(dd, as.character)
g <- graph_from_data_frame(dd, directed = FALSE)
vnames <- as.character(1:nrow(corners))
V(g)[vnames]$row <- corners[,1]
V(g)[vnames]$col <- corners[,2]
plot(g)

然后你可以用

得到row/column信息
as.data.frame(vertex_attr(g))

为了使它们处于正确的形状,您可以设置 xy 属性(这里我翻转了 y 方向,因为 (0,0) 在绘图时位于左下方,但在顶部留在矩阵中)

V(g)[vnames]$y <- -corners[,1]
V(g)[vnames]$x <- corners[,2]

plot(g)

在文本字符串中搜索图形元素的方式不同于@MrFlick。我只在下面显示水平边缘搜索。您需要添加垂直和对角线搜索。

string= "+----------+--------+------------------+
|          |         \                 |
|          |          \                |
|          |           \               |
|          |            +------+-------+
|          |                   |       |
|          |                   |       |
+----------+-------------------+-------+
"

#convert string to ascii numeric matrix
graph_matrix = as.matrix(do.call(rbind, lapply(strsplit(string,"\n")[[1]],utf8ToInt) ))

#scan for horizontal lines
on_line=F
edges= data.frame()
for (row in 1:dim(graph_matrix)[1]) {
  for (col in 1:dim(graph_matrix)[2]) {
    if (graph_matrix[row,col]==45&on_line==F) {
      on_line=T
      edge = c(col-1,row)
    } else if (graph_matrix[row,col]!=45&on_line==T)  {
      on_line=F
      edge = c(edge,col,row)
      edges = rbind(edges,edge)
    }
  }
}
colnames(edges) = c("x1","y1","x2","y2")
edges

#unique vertices
non_unique = mapply(c,edges[,1:2],edges[,3:4])
unique_verts = non_unique[!duplicated(non_unique),]
unique_verts