代码之家  ›  专栏  ›  技术社区  ›  Chris

计算多个多边形之间共享边界的长度

  •  4
  • Chris  · 技术社区  · 7 年前

    require("rgdal")  
    require("rgeos")
    
    download.file("https://www.dropbox.com/s/vbxx9dic34qwz63/Polygons.zip?dl=1", "Polygons.zip")
    
    Shapefile <- readOGR(".","Polygons")
    
    Touching_List <- gTouches(Shapefile, byid = TRUE, returnDense=FALSE)
    
    Touching_DF <- setNames(stack(lapply(Touching_List, as.character)), c("TOUCHING", "ORIGIN"))
    

    Touching_DF 是每个的总长度/周长 ORIGIN 多边形和每个多边形的总长度 TOUCHING 多边形与原始多边形接触。这将允许计算共享边界的百分比。我可以想象,它的输出将是 触摸DF

    编辑1

    gTouches

    下面是一些代码,使用我的实际数据集示例来演示该问题:

    library(rgdal)  
    library(rgeos)
    library(sp)
    library(raster)
    
    download.file("https://www.dropbox.com/s/hsnrdfthut6klqn/Sample.zip?dl=1", "Sample.zip")
    
    unzip("Sample.zip")
    Shapefile <- readOGR(".","Sample")
    
    Touching_List <- gTouches(Shapefile, byid = TRUE, returnDense=FALSE)
    
    # ---- Calculate perimeters of all polygons ----
    perimeters <- sp::SpatialLinesLengths(as(Shapefile, "SpatialLines"))
    
    # ---- All in a lapply loop ----
    all.length.list <- lapply(1:length(Touching_List), function(from) {
      lines <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]],], byid = TRUE)
      if(class(lines) != "SpatialLines"){lines <- lines@lineobj}
      l_lines <- sp::SpatialLinesLengths(lines, longlat=FALSE)
      results <- data.frame(origin = from,
                        perimeter = perimeters[from],
                        touching = Touching_List[[from]],
                        t.length = l_lines,
                        t.pc = 100*l_lines/perimeters[from])
      results
    })
    

    from <- 4
    lines <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]],], byid = TRUE)
    if(class(lines) != "SpatialLines"){lines <- lines@lineobj}
    l_lines <- sp::SpatialLinesLengths(lines, longlat=FALSE)
    
    plot(Shapefile[c(from, Touching_List[[from]]),])
    plot(lines, add = TRUE, col = 1 + 1:length(Touching_List[[from]]), lwd = 2)
    

    我看到的两种可能的解决方案是1。让gTouches只返回长度大于零的共享边或2。当遇到点而不是边时,返回长度为零(而不是错误)。到目前为止,我找不到任何可以做这两件事的东西。

    @StatnMap的修订解决方案非常有效。但是,如果一个多边形与其相邻的多边形不共享捕捉的边界(即,它到达一个点,然后创建一个岛屿滑动多边形),则在之后会出现此错误 lines <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]],], byid = TRUE)

       Error in RGEOSBinTopoFunc(spgeom1, spgeom2, byid, id, drop_lower_td, unaryUnion_if_byid_false,  : 
          Geometry collections may not contain other geometry collections
    

    res

    download.file("https://www.dropbox.com/s/ttg2mi2nq1gbbrq/Bad_Polygon.zip?dl=1", "Bad_Polygon.zip")
    unzip("Bad_Polygon.zip")
    Shapefile <- readOGR(".","Bad_Polygon")
    
    2 回复  |  直到 7 年前
        1
  •  11
  •   Sébastien Rochette    7 年前

    两个只接触自身的多边形的交点是一条线。使用R中的空间库函数可以轻松计算线长度。
    当您从library开始示例时 sp ,您将在该库中找到一个命题。然而,我也给你一个关于新图书馆的建议 sf .

    服务提供商

    require("rgdal")  
    require("rgeos")
    library(sp)
    library(raster)
    
    download.file("https://www.dropbox.com/s/vbxx9dic34qwz63/Polygons.zip?dl=1", "Polygons.zip")
    
    unzip("Polygons.zip")
    Shapefile <- readOGR(".","Polygons")
    
    Touching_List <- gTouches(Shapefile, byid = TRUE, returnDense=FALSE)
    
    # Touching_DF <- setNames(utils::stack(lapply(Touching_List, as.character)), c("TOUCHING", "ORIGIN"))
    
    # ---- Calculate perimeters of all polygons ----
    perimeters <- sp::SpatialLinesLengths(as(Shapefile, "SpatialLines"))
    
    # ---- Example with the first object of the list and first neighbor ----
    from <- 1
    to <- 1
    line <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]][to],])
    l_line <- sp::SpatialLinesLengths(line)
    
    plot(Shapefile[c(from, Touching_List[[from]][to]),])
    plot(line, add = TRUE, col = "red", lwd = 2)
    
    # ---- Example with the first object of the list and all neighbors ----
    from <- 1
    lines <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]],], byid = TRUE)
    l_lines <- sp::SpatialLinesLengths(lines)
    
    plot(Shapefile[c(from, Touching_List[[from]]),])
    plot(lines, add = TRUE, col = 1 + 1:length(Touching_List[[from]]), lwd = 2)
    

    Common frontiers as SpatialLines

    # ---- All in a lapply loop ----
    all.length.list <- lapply(1:length(Touching_List), function(from) {
      lines <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]],], byid = TRUE)
      l_lines <- sp::SpatialLinesLengths(lines)
      res <- data.frame(origin = from,
                        perimeter = perimeters[from],
                        touching = Touching_List[[from]],
                        t.length = l_lines,
                        t.pc = 100*l_lines/perimeters[from])
      res
    })
    
    # ---- Retrieve as a dataframe ----
    all.length.df <- do.call("rbind", all.length.list)
    

    Output table

    t.length 是触摸长度和 t.pc 是相对于原点多边形周长的接触百分比。

    编辑:某些共享边界是点(带有 )

    如前所述,一些边界可能是唯一的点而不是线。为了解释这种情况,我建议将点的坐标加倍,以创建一条长度为0的线。这需要在出现这种情况时逐个计算与其他多边形的交点。

    # Example with the first object of the list and all neighbours
    from <- 4
    lines <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]],], byid = TRUE)
    # If lines and points, need to do it one by one to find the point
    if (class(lines) == "SpatialCollections") {
      list.Lines <- lapply(1:length(Touching_List[[from]]), function(to) {
        line.single <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]][to],])
        if (class(line.single) == "SpatialPoints") {
          # Double the point to create a line
          L1 <- rbind(line.single@coords, line.single@coords)
          rownames(L1) <- letters[1:2]
          Sl1 <- Line(L1)
          Lines.single <- Lines(list(Sl1), ID = as.character(to))
        } else if (class(line.single) == "SpatialLines") {
          Lines.single <- line.single@lines[[1]]
          Lines.single@ID <- as.character(to)
        }
        Lines.single
      })
      lines <- SpatialLines(list.Lines)
    }
    
    l_lines <- sp::SpatialLinesLengths(lines)
    
    plot(Shapefile[c(from, Touching_List[[from]]),])
    plot(lines, add = TRUE, col = 1 + 1:length(Touching_List[[from]]), lwd = 2)
    

    # Corrected for point outputs: All in a lapply loop
    all.length.list <- lapply(1:length(Touching_List), function(from) {
      lines <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]],], byid = TRUE)
      if (class(lines) == "SpatialCollections") {
        list.Lines <- lapply(1:length(Touching_List[[from]]), function(to) {
          line.single <- rgeos::gIntersection(Shapefile[from,], Shapefile[Touching_List[[from]][to],])
          if (class(line.single) == "SpatialPoints") {
            # Double the point to create a line
            L1 <- rbind(line.single@coords, line.single@coords)
            rownames(L1) <- letters[1:2]
            Sl1 <- Line(L1)
            Lines.single <- Lines(list(Sl1), ID = as.character(to))
          } else if (class(line.single) == "SpatialLines") {
            Lines.single <- line.single@lines[[1]]
            Lines.single@ID <- as.character(to)
          }
          Lines.single
        })
        lines <- SpatialLines(list.Lines)
      }
      l_lines <- sp::SpatialLinesLengths(lines)
      res <- data.frame(origin = from,
                        perimeter = perimeters[from],
                        touching = Touching_List[[from]],
                        t.length = l_lines,
                        t.pc = 100*l_lines/perimeters[from])
      res
    })
    
    all.length.df <- do.call("rbind", all.length.list)
    

    这也适用于库 ,但你显然选择了 服务提供商 ,我不会更新此部分的代码。也许以后。。。

    使用库计算多边形共享边界长度 旧金山

    library(sf)
    Shapefile.sf <- st_read(".","Polygons")
    
    # ---- Touching list ----
    Touching_List <- st_touches(Shapefile.sf)
    # ---- Polygons perimeters ----
    perimeters <- st_length(Shapefile.sf)
    
    # ---- Example with the first object of the list and first neighbour ----
    from <- 1
    to <- 1
    line <- st_intersection(Shapefile.sf[from,], Shapefile.sf[Touching_List[[from]][to],])
    l_line <- st_length(line)
    
    plot(Shapefile.sf[c(from, Touching_List[[from]][to]),])
    plot(line, add = TRUE, col = "red", lwd = 2)
    
    # ---- Example with the first object of the list and all neighbours ----
    from <- 1
    lines <- st_intersection(Shapefile.sf[from,], Shapefile.sf[Touching_List[[from]],])
    lines <- st_cast(lines) # In case of multiple geometries (ex. from=71)
    l_lines <- st_length(lines)
    
    plot(Shapefile.sf[c(from, Touching_List[[from]]),])
    plot(lines, add = TRUE, col = 1:length(Touching_List[[from]]), lwd = 2)
    
    # ---- All in a lapply loop ----
    all.length.list <- lapply(1:length(Touching_List), function(from) {
      lines <- st_intersection(Shapefile.sf[from,], Shapefile.sf[Touching_List[[from]],])
      lines <- st_cast(lines) # In case of multiple geometries
      l_lines <- st_length(lines)
      res <- data.frame(origin = from,
                        perimeter = as.vector(perimeters[from]),
                        touching = Touching_List[[from]],
                        t.length = as.vector(l_lines),
                        t.pc = as.vector(100*l_lines/perimeters[from]))
      res
    })
    
    # ---- Retrieve as dataframe ----
    all.length.df <- do.call("rbind", all.length.list)
    
        2
  •  2
  •   FAmorim    5 年前

    只是为了补充斯巴斯蒂安·罗切特的回答,我认为功能 st_length sf 包不适用于多边形(请参见此 post ). 相反,我建议使用函数 st_perimeter 在里面 lwgeom

    (我想对答案发表评论,但我没有足够的声誉)