Извлечь количество перекрестков для идентификаторов путей Open Street Map по типу маршрута
Отредактировано с добавлением дополнительных деталей
У меня есть шейп-файл с 2061 сегментами дороги Open Street Map ( OSM). Каждый сегмент в моем шейп-файле идентифицируется своим 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 - "второстепенными".
Это вторично по отношению к анализу, но в конечном итоге для перекрестков, на которых соединяются несколько типов дорог, я выберу "самый большой" тип дороги. Например, этот узел соединяет служебную дорогу и жилую дорогу; Я хочу выбрать жилую дорогу. Думаю, я могу создать для этого свой собственный список иерархии и разобраться с этим позже.
Я пытаюсь использовать osmdata пакета R Open Sci
Пока я могу добраться до #2 (сигнальные перекрестки), используя osmdata:
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+ идентификаторов путей, которые у меня есть (но если документы или пример существует, укажите мне на него).
Я также открыт для изучения других библиотек инструментов на Python; Пакет Python osmnx имеет то, что кажется отличным инструментом для вычисления "плотности пересечения", но для определенных полигонов, таких как границы городов, и, похоже, не имеет функциональности для создания вызовов в Python по способу или идентификатору узла.
Я также знаю, что, вероятно, я мог бы сделать это в ArcGIS или QGIS, но поскольку у меня уже есть эти идентификаторы OSM в моей базе данных, просто кажется пустой тратой загружать целый чертов шейп-файл пересечений для большого мегаполиса и делать какие-то процесса буферизации, чтобы получить нужную мне информацию. Кроме того, если бы у меня был сценарий, удобный для извлечения некоторых фрагментов информации по пути или идентификатору узла, мне было бы легче расширить типы данных, которые я извлекаю, чтобы получить другие лакомые кусочки отличной информации, записанной для элементов OSM.
Спасибо сообществу пространственных данных!
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 для узлов, описывающих сигналы трафика.
Один из простых способов получить плотность стыков - преобразовать sf
представление автомагистралей в dodgr
сеть, которая представляет собой простой data.frame
где каждая строка является границей сети. Вpoly2line
шаг превращает строгий sf
многоугольники, такие как круговые, в linestring
объекты, а dodgr_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, потому что пешеходные дорожки могут быть довольно сложными, и очевидно, что где-то в Миннеаполисе есть перекресток из семи пешеходных дорожек. Эти идентификаторы являются идентификаторами OSM, поэтому вы можете легко проверить, какие из них находятся вsignal_node_ids
значения, чтобы определить, какие светофоры.
Остальные вопросы, требующие решения:
- Пересечения между однозначно определенными
"highway"
типы просты, но пересечения между разными типами потребуют более сложных модификаций этого кода. Хотя это просто, вам нужно будет последовательно настраиватьdodgr data.frame
в котором ребра направлены:$from_id -> $to_id
. - Связывание сигналов с конкретными соединениями, вероятно, потребует некоторой пространственной буферизации, потому что, как вы предполагаете, одно пересечение может иметь несколько узлов с
"traffic_signals"
. Обратите внимание, что нет "правильного" способа сделать это, потому что, например, перекрестки могут иметь отдельные сигналы для пешеходов и автомобилей, и решение о том, считать ли эти сигналы "одинаковыми", всегда влечет за собой некоторую степень субъективности.
В итоге я создал несколько пользовательских функций, которые полагаются на osmdat
пакет. Я обнаружил, чтоosmdat
позволяет пользователю передавать пользовательские вызовы API в Overpass. После множества проб и ошибок в Overpass Turbo я выяснил синтаксис Overpass "достаточно хорошо", чтобы извлечь нужную мне информацию. Я думаю, что эти три отдельные функции можно было бы объединить в один вызов Overpass API, но это вне меня.
Итак, я сначала создал функцию, чтобы получить список всех способов, которые были связаны с моим "фокусным" путем (1 из 2061 сегмента в моем фрейме данных):
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)
}
Затем я создал функцию для извлечения только идентификаторов узлов для моего основного пути.
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)
}
И третья функция для получения идентификаторов всех путей, пересекающих мой фокус. Эта функция требует в качестве входных идентификаторов узлов фокусного пути.
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 существует, я просто считаю их интуитивно понятными!), Используя эти три функции. Я также попытался провести свой путь через распараллеливание с помощью foreach и doParallel, и разделил мой набор данных на 100 фрагментов по 26 способов в каждом. Это все еще ОЧЕНЬ медленно. Может быть, Overpass API медленный? Скорее всего, я сделал что-то не так при настройке, это мой третий или четвертый раз, когда я использую 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)
Общая логика этого процесса:
- Выберите все WayID из фрагмента кадра данных (1-100)
- Возьмите Way ID ("Основной путь") из моего списка 26 способов в чанке.
- Найдите идентификаторы путей "братьев и сестер" фокусного пути.
- Извлеките идентификаторы узлов, которые составляют основной путь (вместе с информацией о том, где есть сигналы - TY, osmdata
- Для каждого идентификатора узла основного пути найдите идентификаторы путей, которые его пересекают. Также возьмите классификацию этих способов.
- Избавьтесь от "пересекающихся путей", которые на самом деле являются братьями и сестрами Фокального Пути - этих сегментов, которые являются продолжением Фокального Пути. (Например, я бы удалил этот путь из списка пересекающихся путей, если бы мой Focal Way был таким
- rbindlist навсегда
Это должно занять около 2-3 часов для всех сегментов 2061. Это долго; но даже прямые запросы в Overpass Turbo выполняются медленно, так что... может быть, это правильно.