Извлечь количество перекрестков для идентификаторов путей 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 значения, чтобы определить, какие светофоры.


Остальные вопросы, требующие решения:

  1. Пересечения между однозначно определенными "highway"типы просты, но пересечения между разными типами потребуют более сложных модификаций этого кода. Хотя это просто, вам нужно будет последовательно настраиватьdodgr data.frame в котором ребра направлены: $from_id -> $to_id.
  2. Связывание сигналов с конкретными соединениями, вероятно, потребует некоторой пространственной буферизации, потому что, как вы предполагаете, одно пересечение может иметь несколько узлов с "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)

Общая логика этого процесса:

  1. Выберите все WayID из фрагмента кадра данных (1-100)
  2. Возьмите Way ID ("Основной путь") из моего списка 26 способов в чанке.
  3. Найдите идентификаторы путей "братьев и сестер" фокусного пути.
  4. Извлеките идентификаторы узлов, которые составляют основной путь (вместе с информацией о том, где есть сигналы - TY, osmdata
  5. Для каждого идентификатора узла основного пути найдите идентификаторы путей, которые его пересекают. Также возьмите классификацию этих способов.
  6. Избавьтесь от "пересекающихся путей", которые на самом деле являются братьями и сестрами Фокального Пути - этих сегментов, которые являются продолжением Фокального Пути. (Например, я бы удалил этот путь из списка пересекающихся путей, если бы мой Focal Way был таким
    1. rbindlist навсегда

Это должно занять около 2-3 часов для всех сегментов 2061. Это долго; но даже прямые запросы в Overpass Turbo выполняются медленно, так что... может быть, это правильно.

Другие вопросы по тегам