# общие библиотеки
library(tidyverse)
library(magrittr)
# работа с географическими данными
library(osmdata)
library(osrm)
library(sf)
# шкала масштаба на карте
library(ggspatial)
Работа с данными OpenStreetMap
Создание картографической основы важно для многих исследований, использующих географические карты городской инфраструктуры. Одной из самых полных баз данных местоположений является некоммерческий проект OpenStreetMap (сокращенно OSM). Здесь мы покажем, как можно самостоятельно загружать карты, отображать на картах объекты, а также изучим вопрос о степени достижимости возможных объектов пожара пожарно-спасательными подразделениями для оценки рисков. Отметим, что особое внимание при оценке расстояния и времени прибытия подразделениями должно уделяться социально значимым объектам и объектам с массовым пребыванием людей. Аналогом библиотек, использованных здесь, является библиотека OSMnx языка Python, которую разрабатывает Geoff Boeing.
Создание картографической основы
Идея создания карты такого рода рассмотрена в блоге, ее автор – Taras Kaduk.
Загрузим необходимые для работы библиотеки. Для получения данных с OpenStreetMap используется библиотека osmdata
, для работы с географическими данными на основе стандарта Simple Features – библиотека sf
, библиотека osrm
является связующим звеном между R и сервисом OSRM для определения расстояния между объектами, времени движения и кратчайшего пути. Следует отметить, что альтернативный подход для доступа к данным, лежащим в основе OpenStreetMap использует библиотека osmdata, а для массовых извлечений больших наборов данных OSM в сжатом pbf
-формате – библиотека osmextract.
Зафиксируем географический объект, в нашем случае – город Красноярск.
# здесь можно указать любой другой город
<- "Krasnojarsk Russia" my_place
Сделаем запрос из OSM для нахождения географических границ города.
# загрузим все границы, связанные с объектом
1<- opq(my_place, timeout = 300) |>
all_boundaries 2add_osm_feature(key = "boundary",
value = "administrative") |>
3osmdata_sf() |>
4unname_osmdata_sf() %>%
$osm_multipolygons .
- 1
-
делает Overpass-запрос; увеличение параметра
timeout
позволяет делать большие запросы, поскольку время ожидания сервера может истечь до того, как будут доставлены все данные1; - 2
-
добавляет функции в запрос Overpass, которые задаются парами
key-value
; подробное описание значений представлено на wiki OSM, для получения полного списка значений можно использовать командуavailable_features()
, в нашем случае это административные границы: областей, районов, округов и т.д.; - 3
-
возвращает
sf
-объект; - 4
-
удаляет имена из
osmdata
геометрических объектов.
Выясним, где могут содержаться потенциально возможные границы города.
|>
all_boundaries as_tibble() |>
::select(osm_id, name, geom) |>
dplyr::filter(grepl("Красноярск", name)) dplyr
# A tibble: 2 × 3
osm_id name geom
<chr> <chr> <MULTIPOLYGON [°]>
1 190090 Красноярский край (((111.3179 73.85324, 111.3222 73.85231, 1…
2 1157393 городской округ Красноярск (((93.01595 56.06642, 93.01739 56.06589, 9…
Выделим из полученного множества границ только нужную нам, соответствующую границе городского округа.
<- all_boundaries |>
boundary_KRSK ::filter(osm_id == 1157393) dplyr
Другой простой способ получения границы города – воспользоваться библиотекой nominatimlite
для (обратного) геокодирования на основе Nominatim API.
<- nominatimlite::geo_lite_sf(my_place, points_only = FALSE) boundary_KRSK
Теперь получим картографические данные, включающие в себя дорожную сеть улиц, железные дороги и водные объекты – реки и озера. Фиксируем значения ключа highway
.
Код: типы загружаемых дорог
# типы загружаемых дорог и толщина линий на карте
<- tibble::tribble(
highway_sizes ~highway, ~highway_group,
"motorway", "large",
"trunk", "large",
"motorway_link", "large",
"primary", "large",
"primary_link", "large",
"secondary", "medium",
"secondary_link","medium",
"tertiary", "medium",
"tertiary_link", "medium",
"trunk_link", "medium",
"residential", "small",
"living_street", "small",
"unclassified", "small",
"service", "small",
"road", "small",
)
Загрузим дорожную сеть города.2
# streets (улицы)
<- opq(my_place, timeout = 300) |>
streets_osm add_osm_feature(key = "highway",
value = highway_sizes$highway) |>
osmdata_sf() |>
unname_osmdata_sf()
Выделим только типы линий osm_lines
и оставим только улицы, находящиеся внутри границы boundary_KRSK
.
<- streets_osm$osm_lines %>%
streets mutate(length = as.numeric(st_length(.))) |>
left_join(highway_sizes, by = "highway") |>
st_intersection(boundary_KRSK)
|>
streets ::select(osm_id, name, highway,
dplyr|>
maxspeed, oneway, surface) head(5)
Simple feature collection with 5 features and 6 fields
Geometry type: MULTILINESTRING
Dimension: XY
Bounding box: xmin: 92.9288 ymin: 56.0738 xmax: 92.94057 ymax: 56.08547
Geodetic CRS: WGS 84
osm_id name highway maxspeed oneway surface
1 17639885 Северное шоссе primary 60 yes asphalt
2 25375023 Енисейский тракт primary 60 yes asphalt
3 25375024 улица 9 Мая primary 60 yes asphalt
4 25375027 Енисейский тракт primary 60 yes asphalt
5 25375030 Енисейский тракт primary 60 yes asphalt
geom
1 MULTILINESTRING ((92.93389 ...
2 MULTILINESTRING ((92.93911 ...
3 MULTILINESTRING ((92.93978 ...
4 MULTILINESTRING ((92.93999 ...
5 MULTILINESTRING ((92.93936 ...
Загрузим данные соответствующие железным дорогам.
Код: железные дороги
# railways (железные дороги)
<- opq(my_place, timeout = 300) |>
railways_osm add_osm_feature(key = "railway",
value = "rail") |>
osmdata_sf() |>
unname_osmdata_sf()
<- railways_osm$osm_lines |>
railways ::select() dplyr
Получим данные по водным объектам.
Код: водные объекты
# water (водные объекты)
<- opq(my_place, timeout = 300) |>
water_osm add_osm_feature(key = "natural",
value = "water") |>
osmdata_sf() |>
unname_osmdata_sf()
<- opq(my_place, timeout = 300) |>
river_osm add_osm_feature(key = "waterway",
value = c("river",
"riverbank")) |>
osmdata_sf() |>
unname_osmdata_sf()
<-
water c(water_osm, river_osm) %>%
$osm_multipolygons |>
.::select(osm_id, name) dplyr
Создадим базовую карту, нанеся на нее соответствующие объекты.
Код: базовая карта
<- ggplot() +
base_map # дорожная сеть
geom_sf(data = streets |>
::filter(highway_group == "large"),
dplyrlinewidth = 0.4,
color = "grey30") +
geom_sf(data = streets |>
::filter(highway_group == "medium"),
dplyrlinewidth = 0.2,
color = "grey35") +
geom_sf(data = streets |>
::filter(highway_group == "small"),
dplyrlinewidth = 0.1,
color = "grey40") +
# железные дороги
geom_sf(data = railways,
color = "grey30",
linewidth = 0.3,
linetype = "dotdash",
alpha = 0.6) +
# водные объекты
geom_sf(data = water,
fill = "steelblue",
lwd = 0,
alpha = 0.3) +
::theme_ipsum() +
hrbrthemestheme(plot.margin = ggplot2::margin(0.01, 0.01, 0.01, 0.01, "cm"))
Карта-подложка готова, теперь можно использовать ее элементы для отображения всего города или его части. Варьируя границы, мы можем получить карту достаточно подробного масштаба.
+
base_map # границы города
geom_sf(data = boundary_KRSK,
color = "red",
alpha = 0.1) +
# ограничения
coord_sf(xlim = c(92.64, 93.19),
ylim = c(55.90, 56.14),
expand = FALSE)
Дополнительные данные из OpenStreetMap
Напомним, что базовая карта состоит из трех основных составляющих: streets
, railways
и water
. Мы можем использовать информацию из OpenStreetMap для дальнейшего анализа. В частности, переменная speed
объекта streets
отвечает за максимальную разрешенную скорость для различных транспортных средств.
Код: максимальные разрешенные скорости движения
# максимальные разрешенные скорости движения из OpenStreetMap
<- streets |>
streets_speed # рассмотрим только данные без пропусков
::filter(!is.na(maxspeed)) |>
dplyr# заменим коды значениями из OSM
mutate(maxspeed_new =
case_when(
== "RU:living_street" ~ "20",
maxspeed == "RU:rural" ~ "90",
maxspeed == "RU:urban" ~ "60",
maxspeed .default = maxspeed
)
)
# преобразуем переменные
$maxspeed_new <-
streets_speedas.numeric(streets_speed$maxspeed_new)
# создадим факторную переменную для скорости
$max_speed_factor <-
streets_speedfactor(streets_speed$maxspeed_new,
levels = c(5, 8, 10, 20, 30, 40, 49, 50, 60, 70, 90),
labels = c("5 км/ч", "8 км/ч",
"10 км/ч", "20 км/ч",
"30 км/ч", "40 км/ч",
"49 км/ч", "50 км/ч",
"60 км/ч", "70 км/ч",
"90 км/ч"))
|>
streets_speed st_drop_geometry() %>%
count(., max_speed_factor, sort = TRUE)
max_speed_factor n
1 60 км/ч 2556
2 20 км/ч 394
3 40 км/ч 357
4 5 км/ч 83
5 30 км/ч 26
6 90 км/ч 22
7 10 км/ч 6
8 49 км/ч 1
9 50 км/ч 1
10 70 км/ч 1
11 8 км/ч 1
Код: максимальные разрешенные скорости движения (карта)
# карта: максимальная разрешенная скорость движения
+
base_map # дорожная сеть
geom_sf(data = streets_speed,
linewidth = 0.67,
aes(color = max_speed_factor)) +
::scale_color_viridis(discrete = TRUE,
viridisoption = "turbo") +
# географические границы части города
coord_sf(xlim = c(92.8002, 93.0407),
ylim = c(55.9823, 56.1003),
expand = FALSE) +
labs(color = "максимальная \nразрешенная \nскорость") +
theme_void() +
theme(text = element_text(size = 14))
Как видно из рисунка, база OpenStreetMap содержит неполную информацию по каждому участку дорог. Аналогично можно рассмотреть и визуализировать другие характеристики: дороги с односторонним движением (переменная oneway
), тип дорожного покрытия (переменная surface
), количество полос движения (переменная lanes
) и т.д.
Нанесение на карту города различных объектов и ограниченных зон
Нанесем на карту города границы районов. Для этого нужно предварительно загрузить границы из OpenStreetMap, указав значение для ключа admin_level
, в нашем случае значение равно 9 в соответствии с кодированием в таблице деления на административные единицы для OpenStreetMap.
# получение данных по районам города из OSM
<- opq(my_place) |>
districts_osm add_osm_feature(key = "admin_level", value = 9) |>
osmdata_sf() |>
unname_osmdata_sf()
Получим данные по районам, также выделив площадь каждого района.3
# границы районов с площадями
<- districts_osm %>%
districts $osm_multipolygons |>
.::select(osm_id, name) %>%
dplyrmutate(area = st_area(.),
region_area = as.numeric(area))
Найдем центроиды районов.
# центроиды районов
<- cbind(districts,
regions st_coordinates(st_centroid(districts))) |>
rename(c_lon = X, c_lat = Y)
# районы города
regions
Simple feature collection with 7 features and 6 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: 92.62752 ymin: 55.91184 xmax: 93.16844 ymax: 56.13382
Geodetic CRS: WGS 84
osm_id name area region_area c_lon c_lat
1 5854088 Железнодорожный район 11472481 11472481 92.82686 56.01383
2 5854089 Кировский район 23189104 23189104 92.96615 55.98596
3 5854090 Ленинский район 52892774 52892774 93.02779 56.02276
4 5854091 Октябрьский район 90609016 90609016 92.73413 56.01672
5 5854092 Свердловский район 75824721 75824721 92.84575 55.96372
6 5854093 Советский район 88194468 88194468 92.97927 56.07533
7 5854094 Центральный район 34350926 34350926 92.88060 56.04314
geom
1 MULTIPOLYGON (((92.84876 56...
2 MULTIPOLYGON (((92.96183 55...
3 MULTIPOLYGON (((93.04703 56...
4 MULTIPOLYGON (((92.66951 55...
5 MULTIPOLYGON (((92.96183 55...
6 MULTIPOLYGON (((93.09394 56...
7 MULTIPOLYGON (((92.84994 56...
Приведем карту районов города.4
Код: карта районов города
# удалим "район" из названия
$name <- str_remove(regions$name, " район")
regions
set.seed(2024)
+
base_map # районы города
geom_sf(data = regions,
aes(fill = region_area / 1000000),
color = "black",
alpha = 0.5) +
# цвет
::scale_fill_viridis(option = "plasma",
viridisdirection = 1) +
labs(fill = "площадь района (в кв. км):",
x = "", y = "") +
theme(legend.key.size = unit(0.4,"cm"),
legend.key.width = unit(1.8,"cm"),
legend.position = "top") +
# названия районов
::geom_text_repel(data = regions,
ggrepelaes(c_lon, c_lat,
label = name,
color = I("black")),
color = "white",
bg.color = "grey30",
bg.r = 0.15,
size = 5, alpha = 0.9) +
# ограничения
coord_sf(xlim = c(92.64, 93.19),
ylim = c(55.90, 56.14),
expand = FALSE)
Аналогично можно загрузить и отобразить на карте информацию по зданиям и сооружениям.
# buildings (здания)
<- opq(my_place, timeout = 300) |>
buildings_osm add_osm_feature(key = "building") |>
osmdata_sf()
<- buildings_osm %>%
buildings_polygons $osm_polygons
.
<- buildings_osm %>%
buildings_multipolygons $osm_multipolygons
.
<- bind_rows(buildings_polygons,
buildings buildings_multipolygons)
Рассмотрим количество объектов, которые принадлежат улицам города.
count(buildings |>
st_drop_geometry() |>
as_tibble(),
sort = TRUE) |>
addr.street, na.omit()
# A tibble: 1,016 × 2
addr.street n
<chr> <int>
1 проспект им. газеты Красноярский Рабочий 457
2 Центральная улица 401
3 Советская улица 337
4 Лесная улица 334
5 улица Калинина 310
6 улица 60 лет Октября 232
7 улица Карла Маркса 231
8 Новая улица 221
9 Базайская улица 220
10 улица Березина 218
# ℹ 1,006 more rows
Код: улицы города
+
base_map geom_sf(data = buildings, linewidth = 0.15,
aes(fill = `addr:street`,
color = `addr:street`)) +
# географические границы части города
coord_sf(xlim = c(92.8492, 93.0487),
ylim = c(55.9863, 56.0603),
expand = FALSE) +
theme_void() +
theme(legend.position = "none")
Другой пример – построим карту, учитывающую этажность зданий.
Код: этажность зданий
# этажность
+
base_map geom_sf(data = buildings |>
::filter(as.numeric(`building:levels`) >= 0) |>
dplyrmutate(`building:levels` = factor(`building:levels`,
levels = c(0:26, 29, 30))),
linewidth = 0.1,
aes(fill = `building:levels`,
color = `building:levels`)) +
# географические границы города
coord_sf(xlim = c(92.88, 92.95),
ylim = c(56.02, 56.06),
expand = FALSE) +
# шкала аннотаций
annotation_scale(location = "br",
width_hint = 0.5,
style = "ticks") +
theme(legend.position = "right") +
::scale_fill_viridis(option = "turbo",
viridisdiscrete = T) +
labs(fill = "этажность") +
guides(color = "none") +
::theme_ipsum() hrbrthemes
Анализ зон транспортной доступности
Рассмотрим транспортную достижимость объектов по времени движения по транспортной сети. Задачи, связанные с построением зон доступностей, рассмотрены, например, в главе Сетевой анализ курса [Т.Е. Самсонов]. В частности, Федеральный закон от 22.07.2008 № 123-ФЗ (ред. от 25.12.2023) “Технический регламент о требованиях пожарной безопасности” в статье 76 Требования пожарной безопасности по размещению зданий пожарных депо на территориях населенных пунктов говорит о том, что здания пожарных депо на территориях населенных пунктов следует размещать исходя из условия, что время прибытия первого подразделения к месту вызова в городских населенных пунктах не должно превышать 10 минут, в сельских населенных пунктах 20 минут. В связи с этим, актуальным является вопрос достижимости объектов с массовым пребыванием людей (т.е. зданий или сооружений (кроме жилых домов), в котором может одновременно находиться 50 и более человек) из пожарно-спасательных подразделений.
Точки исследования
Помимо карты-подложки нам понадобятся объекты исследования, пусть это будут пожарно-спасательные подразделения, дислоцируемые на территории местного пожарно-спасательного гарнизона (сокращенно ПСЧ). Сформируем точки, которые им соответствуют, это можно сделать случайным образом, например, с помощью команды sf::st_sample()
, либо приписать заданным значениям как в нашем случае – центроидам районов города.
# пожарные части
# точки выбраны как центроиды районов
<-
fire_stations |>
regions mutate(name = c(paste("ПСЧ", 1:7, sep = "-"))) |>
::select(name, c_lon, c_lat) |>
dplyrst_drop_geometry() |>
as_tibble()
fire_stations
# A tibble: 7 × 3
name c_lon c_lat
<chr> <dbl> <dbl>
1 ПСЧ-1 92.8 56.0
2 ПСЧ-2 93.0 56.0
3 ПСЧ-3 93.0 56.0
4 ПСЧ-4 92.7 56.0
5 ПСЧ-5 92.8 56.0
6 ПСЧ-6 93.0 56.1
7 ПСЧ-7 92.9 56.0
Отметим, что в картографии объекты должны рассматриваться в соответствующей географической проекции, которая учитывает искажения Земли. В R библиотека sf
позволяет сделать перевод в географическую систему координат CRS (coordinate reference system).
# CRS (coordinate reference system) проекция в EPSG:4326
<- fire_stations |>
fire_stations_sf st_as_sf(coords = c("c_lon", "c_lat"),
crs = 4326)
fire_stations_sf
Simple feature collection with 7 features and 1 field
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 92.73413 ymin: 55.96372 xmax: 93.02779 ymax: 56.07533
Geodetic CRS: WGS 84
# A tibble: 7 × 2
name geometry
* <chr> <POINT [°]>
1 ПСЧ-1 (92.82686 56.01383)
2 ПСЧ-2 (92.96615 55.98596)
3 ПСЧ-3 (93.02779 56.02276)
4 ПСЧ-4 (92.73413 56.01672)
5 ПСЧ-5 (92.84575 55.96372)
6 ПСЧ-6 (92.97927 56.07533)
7 ПСЧ-7 (92.8806 56.04314)
Предположим, что нам необходимо решить задачу распределения районов выезда пожарных частей в терминах евклидова расстояния, без учета движения вдоль дорог и времени движения пожарного автомобиля. Тогда задача решалась бы используя диаграммы Вороного. Напомним, что диаграмма Вороного конечного множества точек S на плоскости представляет такое разбиение плоскости на ячейки, при котором каждая область этого разбиения образует множество точек, более близких к одному из элементов множества S, чем к любому другому элементу множества. Ниже показан пример такого разбиения, альтернативный вариант можно осуществить с помощью библиотеки ggforce.
Код: диаграммы Вороного
# диаграммы Вороного
+
base_map geom_sf(data = fire_stations_sf |>
st_combine() |>
st_voronoi() |>
st_collection_extract() |>
st_intersection(boundary_KRSK),
color = "darkblue",
linewidth = 0.7,
alpha = 0.01) +
# точки с ПСЧ
geom_sf(data = fire_stations_sf,
color = "magenta4",
alpha = 1,
shape = 8,
size = 3,
stroke = 1.2) +
# маркеры
::geom_label_repel(data = fire_stations,
ggrepelaes(c_lon, c_lat, label = name),
fontface = "bold",
size = 4, alpha = 0.9) +
# географические границы части города
coord_sf(xlim = c(92.72, 93.04),
ylim = c(55.96, 56.09),
expand = FALSE) +
# шкала аннотаций
annotation_scale(location = "tl",
width_hint = 0.5,
style = "ticks") +
labs(x = "", y = "")
Выберем учебные заведений города Красноярска в качестве примера объектов исследования и проанализируем, все ли из этих зданий находятся в 10-минутной зоне доступности из ПСЧ.
# школы, находящиеся внутри границы города
<- buildings |>
schools ::filter(building %in% c("school")) |>
dplyrst_intersection(boundary_KRSK)
Вместо самих зданий мы выберем их центроиды.
# центроиды объектов
<- st_coordinates(st_centroid(schools)) |>
schools_points_KRSK rename(sch_lon = X, sch_lat = Y) |>
::select(sch_lon, sch_lat) |>
dplyrst_drop_geometry() |>
st_as_sf(coords = c("sch_lon", "sch_lat"), crs = 4326)
Достижимость объектов на основе матрицы расстояний
Получим матрицу расстояний между ПСЧ и учебными заведениями города с использованием библиотеки osrm, являющейся интерфейсом между R и сервисом маршрутизации OSRM на основе OSRM API.
# матрица расстояний между объектами
= osrmTable(src = fire_stations_sf,
distancetable dst = schools_points_KRSK)
Преобразуем таблицу расстояний.
<-
schools_distances |>
schools_points_KRSK mutate(mintime = distancetable$durations |>
as_tibble() |>
summarise(across(where(is.numeric), min)) |>
t() %>%
as.vector(.))
Отметим, что альтернативным подходом для нахождения матрицы, которая отражает расстояния либо время движения между объектами является использование библиотеки r5r
– интерфейсу к сервису \(R^5\), что расшифровывается как Rapid Realistic Routing on Real-world and Reimagined networks и библиотеки accessibility, которые взяты в основу книги [R.H.M. Pereira, D. Herszenhut].
Нанесем на карту результат, выделив цветом расчетное время прибытия и формой значка те учебные заведения, где время прибытия превысит расчетные 10 минут (треугольник).
Код: достижимость объектов
set.seed(2024)
+
base_map # нанесение школ
geom_sf(data = schools_distances,
aes(fill = mintime,
# форма значка
shape = I(ifelse(mintime >= 10, 24, 22))),
alpha = 0.95, size = 3.7
+
) # цвет
::scale_fill_viridis(option = "turbo",
viridisbreaks = c(1, 5, 10, 15),
labels = c("1 мин", "5 мин",
"10 мин", "15 мин"),
limits = c(0, max(schools_distances$mintime))) +
# точки с ПСЧ
geom_sf(data = fire_stations_sf,
color = "magenta4",
alpha = 1,
shape = 8,
size = 3,
stroke = 1.2) +
# маркеры
::geom_label_repel(data = fire_stations,
ggrepelaes(c_lon, c_lat, label = name),
fontface = "bold",
size = 4, alpha = 0.9) +
# географические границы части города
coord_sf(xlim = c(92.79, 93.04),
ylim = c(55.97, 56.08),
expand = FALSE) +
# шкала аннотаций
annotation_scale(location = "tl",
width_hint = 0.5,
style = "ticks") +
# оформление темы
theme(legend.key.size = unit(0.5,"cm"),
legend.key.width = unit(2,"cm"),
legend.position = "top",
plot.margin = ggplot2::margin(0.01, 0.01, 0.01, 0.01, "cm")) +
labs(x = "", y = "",
fill = "время прибытия:")
Очевидно, что не все учебные заведения города при данной условной расстановке ПСЧ являются достижимыми за отведенное время 10 минут.
|>
schools_distances mutate(
late_category = case_when(
> 10 ~ "> 10 мин",
mintime <= 10 ~ "<= 10 мин"
mintime
)|>
) ::tabyl(late_category) |>
janitor::adorn_pct_formatting(digits = 0) |>
janitor::set_names("категория", "количество", "процент") purrr
категория количество процент
<= 10 мин 154 81%
> 10 мин 36 19%
Несмотря на то, что мы решили задачу по анализу и визуализации доступности объектов относительно выбраанных объектов, наше решение обладает рядом существенных недостатков. Во-первых, для большого количества объектов мы не сможем осуществить большой объем запросов (максимум – один запрос в секунду, никакого веб-скреппинга и т.д.). Кроме того, здесь мы не можем самостоятельно настраивать скорости движения автомобиля по различным типам дорог.
Одним из оптимальных решений является упрощение дорожной сети с помощью библиотек sfnetworks, dodgr, либо OSMnx в Python до графа дорожной сети, что делает настройки более гибкими и позволяет использовтаь алгоритмы теории графов. Этот подход будет рассмотрен в другой статье.
Построение оптимальных маршрутов
Покажем, как можно строить оптимальные маршруты с помощью библиотеке osrm
. Выберем две произвольных точки, которые будут соответствовать учебному заведению и пожарной части.
set.seed(2024)
<- fire_stations_sf |>
fire_stations_sample sample_n(1)
<- schools_points_KRSK |>
schools_sample mutate(id = row_number()) |>
sample_n(1)
Построим оптимальный маршрут движения между от первой до второй выбранной точки пользуясь командой osrmRoute
.
<- osrmRoute(src = fire_stations_sample,
route dst = schools_sample,
overview = "full")
route
Simple feature collection with 1 feature and 4 fields
Geometry type: LINESTRING
Dimension: XY
Bounding box: xmin: 92.78692 ymin: 55.97694 xmax: 92.96585 ymax: 56.01863
Geodetic CRS: WGS 84
src dst duration distance geom
1 1 1 22.28 16.3655 LINESTRING (92.96585 55.985...
Мы видим, что команда osrmRoute()
нашла расчетное время движения duration
и длинуdistance
маршрута. На карте ниже можно увидеть построенный маршрут.
Код: построение маршрута
# построение маршрута
set.seed(2024)
+
base_map # здания
geom_sf(data = buildings, linewidth = 0.05) +
# маршрут
geom_sf(data = route,
color = "red",
linewidth = 1) +
# ПСЧ -- исходная точка
geom_sf(data = fire_stations_sample,
color = "magenta4",
alpha = 1,
shape = 8,
size = 3,
stroke = 1.3) +
# школа -- конечная точка
geom_sf(data = schools_sample,
color = "darkblue",
alpha = 1,
shape = 8,
size = 3,
stroke = 1.3) +
# ярлык
geom_sf_label(data = fire_stations_sample,
aes(label = name),
nudge_y = -0.003,
fontface = "bold",
size = 4, alpha = 0.9) +
# географические границы города
coord_sf(xlim = c(92.77, 92.99),
ylim = c(55.97, 56.03),
expand = FALSE) +
theme_void() +
labs(x = "", y = "")
Заметим, что фукция osrmIsochrone()
в osrm
позволяет находить изохроны (области, ограниченные фиксированным временем движения), тем не менее, она обладает теми же недостатками, о которых говорилось выше; кроме того, область построения изохроны может быть неодносвязной, некорректно вычисляться и т.д., даже несмотря на варьирования параметра res
отвечающего за разрешение.
Заключение
В статье были кратко рассмотрены следующие вопросы в применении к географическим данным городской инфраструктуры:
- построение OpenStreetMap карт городской структуры дорог, нанесение точечных объектов и ограниченных областей на карты;
- достижимость объектов из пожарно-спасательных подразделений по временному показателю;
- построение оптимальных маршрутов движения автомобиля.
Все представленные здесь результаты являются воспроизводимыми, заменив соответствующие значения, легко рассмотреть подобные карты для других городов.
Существует множество инструментов для решения такого рода задач как на языке R, так и на языке Python, и библиотека osrm
является не единственной библиотекой в этом классе.
─ Session info ───────────────────────────────────────────────────────────────
setting value
version R version 4.3.2 (2023-10-31)
os macOS Monterey 12.1
system aarch64, darwin20
ui X11
language (EN)
collate ru_RU.UTF-8
ctype ru_RU.UTF-8
tz Asia/Krasnoyarsk
date 2024-03-01
pandoc 2.18 @ /Users/materov/opt/miniconda3/envs/ox/bin/ (via rmarkdown)
quarto 1.4.550
─ Packages ───────────────────────────────────────────────────────────────────
package * version date (UTC) lib source
dplyr * 1.1.4 2023-11-17 [1] CRAN (R 4.3.1)
forcats * 1.0.0 2023-01-29 [1] CRAN (R 4.3.0)
ggplot2 * 3.5.0 2024-02-23 [1] CRAN (R 4.3.1)
ggspatial * 1.1.9 2023-08-17 [1] CRAN (R 4.3.0)
lubridate * 1.9.3 2023-09-27 [1] CRAN (R 4.3.1)
magrittr * 2.0.3 2022-03-30 [1] CRAN (R 4.3.0)
osmdata * 0.2.5 2023-08-14 [1] CRAN (R 4.3.0)
osrm * 4.1.1 2023-03-30 [1] CRAN (R 4.3.0)
purrr * 1.0.2 2023-08-10 [1] CRAN (R 4.3.0)
readr * 2.1.5 2024-01-10 [1] CRAN (R 4.3.1)
sessioninfo * 1.2.2 2021-12-06 [1] CRAN (R 4.3.0)
sf * 1.0-15 2023-12-18 [1] CRAN (R 4.3.1)
stringr * 1.5.1 2023-11-14 [1] CRAN (R 4.3.1)
tibble * 3.2.1 2023-03-20 [1] CRAN (R 4.3.0)
tidyr * 1.3.1 2024-01-24 [1] CRAN (R 4.3.1)
tidyverse * 2.0.0 2023-02-22 [1] CRAN (R 4.3.0)
[1] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library
──────────────────────────────────────────────────────────────────────────────
Сноски
Можно также установить глобальную опцию
options(timeout = max(300, getOption("timeout")))
.↩︎Данные по дорожной сети из OSM могут содержать некорректные объекты, например, объектам могут соответствовать неверные координаты, что может приводит к смещениям объектов. В этом случае все неточности можно исправить с помощью функций библиотеки
sf
.↩︎Отметим, что для некоторых населенных пунктов также следует рассмотреть
.$osm_polygons
при нахождении границ районов.↩︎Для некоторых городов (например, Новосибирска) районы могут быть разделены на несколько компонентов, что нужно учитывать при нахождении центроидов и нанесение их на карты.↩︎
Ссылка для цитирования
@online{матеров2021,
author = {Матеров, Е.Н.},
title = {Анализ географически распределенных объектов городской
инфраструктуры},
date = {2021-11-22},
url = {https://www.naukaidannye.netlify.app/blog/posts/2021-11-22-spatial},
langid = {ru}
}