关于 openstreetmap:按路线类型提取 Open Street Map 方式 ID 的交叉口计数

Extract count of intersections for Open Street Map way IDs, by route type

编辑添加了额外的细节

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

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

1
2
3
4
5
6
7
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(信号交叉口)

1
2
3
4
5
6
7
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 元素记录的其他重要信息。

感谢空间数据社区!


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

1
2
3
4
5
6
7
8
9
10
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 步骤将严格的 sf 多边形(例如环形交叉路口)转换为 linestring 对象,而 dodgr_contract_graph() 调用将网络简化为仅交汇点。

1
2
3
4
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) 将为您提供不同类型高速公路的频率。然后,您可以检查特定类型的结频率如下

1
2
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 值中以确定哪??些具有交通信号。

需要解决的剩余问题:

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    get_related_ways <- function(wayid, bboxstring){
      related_ways_query <-
        paste0("[bbox:", bboxstring,"];\
    ",
              "way(id:", wayid,");\
    ",
              "rel(bw);\
    ", # get all sibling
              "way(r);\
    ",
              "out;")

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

    然后我创建了一个函数来仅提取我的焦点路径的节点 ID。

    1
    2
    3
    4
    5
    6
    7
    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。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    get_intersecting_ways <- function(nodeid, bboxstring){

      node_to_intways_query <-
        paste0("[bbox:", bboxstring,"];\
    ",
              "node(id:", nodeid,");\
    ",
              "way(bn)[highway];\
    ",
              "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 存在,我只是觉得它们很直观!)。我还尝试通过使用 foreach 和 doParallel 并行化这个方法,并将我的数据集分成 100 个块,每个块有 26 种方式。它仍然很慢。 Overpass API 可能很慢?更有可能我在设置时做错了,这只是我第三次或第四次使用 doParallel。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    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)

    这个过程的一般逻辑是:

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

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