1

编辑添加了额外的细节

我有 2,061 个开放街道地图 ( OSM ) 路段的 shapefile。我的 shapefile 中的每个段都由其 OSM Way ID 标识。

这是我的数据中五个段的示例:

data = 
structure(list(osm_id = structure(1:5, .Label = c("17990110", 
"17993246", "17994983", "17994985", "17995338"), class = "factor"), 
    name = structure(c(1L, 3L, 4L, 5L, 2L), .Label = c("109th Avenue Northeast", 
    "85th Avenue Northeast", "Bunker Lake Boulevard Northeast", 
    "Foley Boulevard", "Northdale Boulevard Northwest"), class = "factor")), row.names = c(NA, 
5L), class = c("sf", "data.frame"))

对于这 2061 个路段中的每一个路段,我想分别计算每种道路类型(住宅、主要、第三...)与其他道路的交叉口数量。

例如,这条 OSM 路与其他 27 条路相交,其中 11 条为“住宅”,其中 3 条为“二级”高速公路。

这是次要的分析,但最终,对于多种类型道路连接的交叉口,我将选择“最大”类型的道路。例如,该节点连接了一条服务道路和一条住宅道路;我想选择住宅道路道路。我想我可以为此创建自己的层次结构列表并在以后处理它。

我正在尝试使用 R Open Sci 包osmdata

到目前为止,我可以使用 osmdata 到达#2(信号交叉口):

node_dat <- opq_osm_id(type = "node", id = '17990110')%>%
  opq_string()%>%
  osmdata_sf

node_dat_pts <- node_dat$osm_points

nrow(node_dat_pts[node_dat_pts$traffic_signals %in% "signal",])

这表明沿着这个 OSM 段有三个交通信号。(尽管实际上只有两个信号交叉口——两个信号与分开的高速公路相关联……但这可能是另一个问题)。

我是 R 本地人,这就是 osmdata 包对我如此有吸引力的原因,但我愿意探索 Overpass API 中的查询。TBH 我发现关于如何在网站上获取交叉节点的示例不太适用——而且我不清楚如何将此过程扩展到我拥有的 2000 多个方式 ID 的长列表(但如果文档或存在示例,请指出它)。

我也愿意探索 Python 中的其他工具库;Python osmnx 包似乎具有用于计算“交叉点密度”的优秀工具,但对于指定的多边形,如城市边界,并且似乎没有通过方式或节点 ID 在 Python 中创建调用的功能。

我也知道我可能可以在 ArcGIS 或 QGIS 中执行此操作,但是因为我的数据库中已经有了这些 OSM ID,所以为大都市地区加载一个完整的交叉路口形状文件并做一些事情似乎是一种浪费缓冲过程以获取我需要的信息。另外,如果我有一个方便的脚本来通过方式或节点 ID 提取一些信息,我可以更轻松地扩展我提取的数据类型,以获取为 OSM 元素记录的其他重要信息的花絮。

感谢空间数据社区!

4

2 回答 2

2

始终标记交通信号"highway" = "traffic_signals",但也可以使用 的键标记单个节点"traffic_signals"。因此,获取所有交通信号的第一步可以这样完成:

library(osmdata)
hw <- opq("minneapolis") %>%
    add_osm_feature(key = "highway") %>%
    osmdata_sf()
signal_nodes <- opq("minneapolis") %>%
    add_osm_feature(key = "traffic_signals") %>%
    osmdata_sf()
index <- which (!is.na (hw$osm_points$traffic_signals) |
                grepl ("traffic_signals", hw$osm_points$highway)) # grepl because can be "traffic_signals:<value>"
signal_node_ids <- unique (c (signal_nodes$osm_points$osm_id, hw$osm_points$osm_id [index]))

然后保存描述交通信号的节点的所有 OSM ID 值。

获得交叉点密度的一种直接方法是将sf高速公路的表示转换为dodgr网络,这很简单data.frame,每一行都是网络边缘。该步骤将诸如环形交叉路口之类的poly2line严格多边形转换为对象,而该调用仅将网络简化为交汇点。sflinestringdodgr_contract_graph()

library(dodgr)
hw <- osm_poly2line(hw)$osm_lines %>%
    weight_streetnet(keep_cols = "highway", wt_profile = 1) %>% # wt_profile irrelevant here
    dodgr_contract_graph()

table(net$highway)会给你不同类型的高速公路的频率。然后,您可以检查特定类型的结频率,如下所示

net_footway <- net[net$highway == "footway", ]
table(table(c(net_footway$from_id, net_footway$to_id)))

值为 1 表示单向终端节点;值为 2 表示双向终端节点;值 4 表示两条边之间的交叉点;等等。那张桌子增加到 14 条,因为人行道可能相当复杂,而且明尼阿波利斯某处显然有7 条人行道的交汇处。这些 ID 是 OSM id,因此您可以轻松检查其中哪些在signal_node_ids值中以确定哪​​些具有交通信号。


需要解决的剩余问题:

  1. 单一定义"highway"类型之间的交集很容易,但不同类型之间的交集需要对这段代码进行更复杂的修改。尽管很简单,但您需要始终如一地设置dodgr data.frame边缘指向的子集:$from_id -> $to_id.
  2. 将信号与特定路口相关联可能需要某种空间缓冲,因为正如您所建议的那样,单个路口可能有多个节点"traffic_signals"。请注意,没有“正确”的方法可以做到这一点,因为例如,交叉路口可能对行人和汽车有单独的信号,并且决定是否将这些信号视为“相同”的信号总是需要一定程度的主观性。
于 2020-03-03T10:08:20.487 回答
0

我最终创建了一些依赖osmdat包的自定义函数。我发现它osmdat允许用户将自定义 API 调用传递给 Overpass。在Overpass Turbo中经过大量试验和错误后,我发现 Overpass 语法“足够好”以提取我需要的信息。我认为这三个独立的函数可以组合成一个 Overpass API 调用,但这超出了我的范围。

所以我首先创建了一个函数来获取与我的“焦点”方式相关的所有方式的列表(我的数据框中的 2,061 个段中的 1 个):

get_related_ways <- function(wayid, bboxstring){
  related_ways_query <- 
    paste0("[bbox:", bboxstring,"];\n",
           "way(id:", wayid, ");\n",
           "rel(bw);\n", # get all sibling 
           "way(r);\n",
           "out;")

  related_ways <- osmdata_sf(related_ways_query)
  related_ways <- related_ways$osm_lines
  related_ways <- related_ways$osm_id
  return(related_ways)
}

然后,我创建了一个函数来仅为我的焦点方式提取节点 ID。

get_nodes_from_way <- function(wayid){
  nodes_from_way <- opq_osm_id(id = wayid, "way")%>%osmdata_sf()
  nodes_from_way <- nodes_from_way$osm_points
  nodes_from_way$geometry <- NULL
  setnames(nodes_from_way, old = 'osm_id', new = 'node_from_way_id')
  return(nodes_from_way)
}

第三个功能是获取与我的焦点相交的所有方式的 ID。此功能需要输入焦点路径的节点 ID。

get_intersecting_ways <- function(nodeid, bboxstring){

  node_to_intways_query <- 
    paste0("[bbox:", bboxstring,"];\n",
           "node(id:", nodeid, ");\n",
           "way(bn)[highway];\n",
           "out;")

  intways_from_node <- osmdata_sf(node_to_intways_query)
  intways_from_node <- intways_from_node$osm_lines
  intways_from_node$geometry <- NULL
  intways_from_node <- intways_from_node[,names(intways_from_node) %in%c("osm_id", "name", "highway", "ref")]

  setnames(intways_from_node, old = 'osm_id', new = 'intersecting_way_id')
  setnames(intways_from_node, old = 'highway', new = 'intersecting_way_type')
  setnames(intways_from_node, old = 'ref', new = 'intersecting_way_ref')
  setnames(intways_from_node, old = 'name', new = 'intersecting_way_name')

  return(intways_from_node)
}

对于所有三个函数,我都有一个“bboxstring”或边界框字符串传递给 Overpass,希望加快查询速度。哈。希望...

无论如何。我使用这三个函数创建了一个嵌套的 for 循环(不要评判我,我知道 purrr 存在,我只是觉得它们很直观!)。II 还尝试通过使用 foreach 和 doParallel 并行化这个方法,并将我的数据集分成 100 个块,每个块有 26 种方式。它仍然很慢。Overpass API 可能很慢?更有可能我在设置时做错了,这只是我第 3 次或第 4 次使用 doParallel。

for(this_part in unique(cmp_osmdat$partnum)){

osm_character_ids <- as.character(cmp_osmdat$osm_id)

  # test:
  # osm_character_ids <- osm_character_ids[1:3]

  # for each parallel process to get our intersecting ways ("all ways")
  all_ways <- 
    foreach(w = seq_along(osm_character_ids), 
            # require list of packages from above:
            .packages = packs, 
            .errorhandling = "remove", # remove data frames that throw an error
            # print the stuff: 
            .verbose = TRUE) %dopar% { 

              environmentIsLocked(asNamespace("curl"))
              unlockBinding(sym = "has_internet", asNamespace("curl"))
              assign(x = "has_internet", value = {function() T}, envir = asNamespace("curl"))


              this_way_id <- osm_character_ids[[w]]

              # find ways that are related to this one (same road, different segments)
              # so that we can filter these out as intersections:
              these_related_ways <- get_related_ways(this_way_id, this_bbox_string)

              # get nodes of this way:
              these_nodes_from_way <- get_nodes_from_way(this_way_id)

              # adding a column to store this way id, for easy rbind later 
              # (foreach doesn't store list names?)
              these_nodes_from_way$way_id <- this_way_id

              # create an empty list to store interesecting ways for each node:
              these_intersecting_ways <- list()

              # get intersecing ways from nodes: 
              for(n in seq_along(these_nodes_from_way$node_from_way_id)){
                this_node <- these_nodes_from_way$node_from_way_id[[n]]
                # put intersecting ways into our empty list (the name of the list item will be the node ID)
                these_intersecting_ways[[this_node]] <- get_intersecting_ways(this_node, this_bbox_string)
              } # end get intersecting ways from node

              # combine intersecting ways of each node into one data table:
              these_intersecting_ways_df <- rbindlist(these_intersecting_ways, idcol = 'node_from_way_id', use.names = T, fill = T)

              # get rid of intersections with this way's realtives (other segments of the same road):
              these_intersecting_ways_df <- these_intersecting_ways_df[!these_intersecting_ways_df$intersecting_way_id %in% these_related_ways,]

              # to get node information, merge intersecting ways to our node data: 
              nodes_and_ways <- merge(these_intersecting_ways_df, these_nodes_from_way, by = 'node_from_way_id')

              # return node and intersection data
              return(nodes_and_ways)

            } # end foreach

  nodes_and_ways_df <- rbindlist(all_ways, use.names = T, fill = T)

  # save file, one for each part (results in 10 csvs)
  write.csv(nodes_and_ways_df, 
            file = paste0("intersection_density_results/intersection-density-data-part-", this_part, ".csv"), row.names = F)

} # end 10 parts

stopCluster(cl)

这个过程的一般逻辑是:

  1. 从数据框块中选择所有 WayID (1-100)
  2. 从我的块中 26 种方式的列表中获取一个方式 ID(“焦点方式”)
  3. 查找焦点路径的“兄弟”的路径 ID。
  4. 提取构成焦点路径的节点 ID(以及有关信号所在位置的信息 - TY、osmdata
  5. 对于焦点路径的每个节点 ID,找到与其相交的路径的路径 ID。还有抓分类的那些方法。
  6. 摆脱实际上是焦点方式的兄弟的“交叉方式”——这些部分是焦点方式的延续。(例如,如果我的焦点方式是这种方式,我将从相交方式列表中删除这种方式
    1. rbindlist 永远

运行所有 2061 段大约需要 2-3 小时。那是很长一段时间;但是,即使是 Overpass Turbo 中的直接查询也很慢,所以......也许这是正确的。

于 2020-03-04T21:11:35.683 回答