Извлечь количество пересечений для идентификаторов пути Open Street Map по типу маршрута - PullRequest
1 голос
/ 02 марта 2020

Отредактировано с добавлением дополнительной информации

У меня есть шейп-файл из 2061 сегмента дороги Open Street Map ( OSM ). Каждый сегмент в моем шейп-файле идентифицируется по идентификатору пути OSM.

Вот пример из пяти сегментов из моих данных:

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 package osmdata

Пока я могу получить в # 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 есть три сигнала traffi c. (Хотя в действительности существует только два сигнальных пересечения - два сигнала связаны с разделенной магистралью ... но это может быть проблемой в другой раз).

Я - уроженец R, который Вот почему пакет osmdata настолько привлекателен для меня, но я открыт для изучения запросов в Overpass API. TBH Я нашел пример о том, как получить узлы пересечения на веб-сайте, не совсем применимый, и я не знаю, как масштабировать этот процесс до длинного списка 2000+ идентификаторов пути, которые у меня есть ( но если есть документы или пример, укажите мне на это).

Я также открыт для изучения других библиотек инструментов в Python; пакет Python osmnx имеет то, что кажется отличными инструментами для расчета "плотности пересечений", но для определенных полигонов, таких как границы города, и, кажется, не имеет функциональности для создания вызовов в Python путем или идентификатором узла.

Я также знаю, что, вероятно, я мог бы сделать это в ArcGIS или QGIS, но, поскольку у меня уже есть эти идентификаторы OSM в моей базе данных, это просто пустая трата, чтобы загрузить весь дан шейп-файл пересечений для большого столичного региона и выполнение некоторого процесса буферизации для получения необходимой мне информации. Кроме того, если бы у меня был сценарий для извлечения некоторых фрагментов информации по пути или идентификатору узла, я мог бы легче расширить виды извлекаемых данных, чтобы получить другие небольшие фрагменты важной информации, записанной для элементов OSM.

Спасибо сообщество пространственных данных!

Ответы [ 2 ]

2 голосов
/ 03 марта 2020

Traffi c сигналы должны всегда быть помечены "highway" = "traffic_signals", но отдельные узлы также могут быть помечены ключом "traffic_signals". Таким образом, первый шаг для получения всех сигналов traffi c может быть выполнен следующим образом:

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 узлов, описывающих сигналы traffi c.

Одним простым способом получения плотностей соединений является преобразование представления 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, чтобы определить, какие из них имеют сигналы traffi c.


Остальные проблемы по адресу:

  1. Пересечения между единственно определенными типами "highway" просты, но пересечения между различными типами потребуют более сложных модификаций этого кода. Хотя это и просто, вам необходимо последовательно установить dodgr data.frame, в котором направлены ребра: $from_id -> $to_id.
  2. Связывание сигналов с конкретными переходами, вероятно, потребует некоторой пространственной буферизации, потому что, как вы предлагаете одно пересечение может иметь несколько узлов с "traffic_signals". Обратите внимание, что не существует «правильного» способа сделать это, потому что, например, перекрестки могут иметь отдельные сигналы для пешеходов и автомобилей, и решение о том, считать ли они «одинаковыми» сигналами, всегда будет в какой-то степени субъективным.
0 голосов
/ 05 марта 2020

В итоге я создал несколько пользовательских функций, основанных на пакете osmdat. Я обнаружил, что osmdat позволяет пользователю передавать пользовательские вызовы API в Overpass. После большого количества проб и ошибок в Overpass Turbo я выяснил синтаксис Overpass "достаточно хорошо", чтобы извлечь необходимую мне информацию. Я думаю, что эти три отдельные функции могут быть объединены в один вызов API Overpass, но это не для меня.

Поэтому я сначала создал функцию, чтобы получить список всех способов, которые были связаны с моим "фокусным" способом (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, в надежде ускорить запросы. Ха. Надеюсь ...

В любом случае. Я создал вложенное для l oop (не судите меня, я знаю, что purrr существует, я просто нахожу их интуитивным!), Используя эти три функции. Я также попытался объединить мой путь через распараллеливание этого, используя 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)

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

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

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

...