10 Работа с геоданными: leaflet
10.1 Векторная и растровая графика
Перед тем как обсуждать карты, следует сначала обсудить разницу между векторной и растровой графикой.
- Растровые изображения представляют собой набор упорядоченных пикселей, про каждый из которых храниться информация о цвете. Векторное изображение нельзя бесконечно увеличивать — в какой-то момент станут видны пиксели, которые в каком-то смысле являются пределом увелечения. Наиболее популярные форматы растровых изображений:
JPEG
,GIF
,PNG
,BMP
,TIFF
и другие. - В векторных изображениях инормация храниться как собрани точек, линий и полигонов в некоторой системе координат, что позволяет бесконечно увеличивать такие изображения не теряя в качестве. Наиболее популярные форматы векторных изображений:
PDF
,SVG
,EPS
и другие.
Современные технологии позволяют соединять растровые и векторные изображения, а также трансформировать их друг в друга. Картографические данные могут попадать в разные типы: точки (столицы всех стран), линии (улицы в каком-нибудь городе), полигоны (границы стран и меньших регионов) обычно имеют некоторую геопривязку (для простоты давайте считать такими, все, что имеет широту и долготу), так что могут быть представлены векторно, однако существует достаточно много информации, которую невозможно представить никак подругому, кроме как векторно: спутниковые снимки, существующие физические/политические/климатические/исторические и т. п. карты, выдача картографических сервисов, таких как Google Maps. Кроме того, занимаясь любыми типами визуализации следует помнить о разнице статической визаулизации, которую после создания нельзя изменить, и динамической визуализации, которая позволяет пользователям изменять себя (увеличиваться и уменьшаться, кликать на собрание точек и видеть их значения и т. п.). В данной главе, в отличие от предыдущих мы сосредоточимся на пакете для динамичского картографирования leaflet
. Достаточно много тем останется за пределами этой главы: изменение проекции, манипуляции с географическими данными, работа с растровыми изображениями и другие (см., например, (Lovelace, Nowosad, and Muenchow 2019), доступная здесь).
10.2 Картографические примитивы
В картографии существуют свои элементарные единицы:
Эти единицы поддерживают популярные пакеты для манипуляции с георграфическими объектами: sp
, sf
и другие. В данном разделе мы не будем учиться операциям с этими объектами (объединение, вычитание и т. п., подробности смотрите в документации к пакету sp
или в уже упомянавшейся книжке (Lovelace, Nowosad, and Muenchow 2019)).
10.3 leaflet
Для начала включим библиотеку:
Здесь доступен cheatsheet, посвященный пакету leaflet
.
10.3.1 .csv
файлы
Источником географических данных могут быть обычные привычные нам csv файлы. Например, вот здесь, хранится датасет из проекта The Unwelcomed Мохамада А. Вэйкда (Mohamad A. Waked), содержащий информацию о месте и причинах смерти мигрантов и беженцев по всему миру с января 2014 года по июнь 2019 года.
unwelcomed <- read_csv("https://raw.githubusercontent.com/agricolamz/DS_for_DH/master/data/death_of_migrants_and_refugees_from_the_Unwelcomed_project.csv")
id
— идентификационный номер;date
— дата происшедшего;total_death_missing
— количество погибших/пропавших;location
— место происшедшего;lat
— широта;lon
— долгота;collapsed_region
— обобщенная информация о регионе;region
— информация о регионе;collapsed_cause
— обобщенная информация о причине смерти;cause_of_death
— информация о причине смерти.
Информация о широте и долготе иногда записывают в градусах, минутах и секундах, а иногда в десятичной записи, в R обычно используется десятичная запись. В интернете легко найти конвертеры из одного формата в другой и обратно.
Самый простой способ нанести на карту координаты, это использовать комбинацию функций leaflet() %>% addCircles()
:
unwelcomed %>%
leaflet() %>%
addCircles(lng = ~lon, # обратите внимание на особый синтаксис с тильдой
lat = ~lat)
Чтобы точки не “висели в воздухе” можно добавить подложку:
Функция addCircles()
имеет массу аргументов, которая отвечает за отображение:
radius
color
opacity
fill
fillColor
label
popup
К сожалению, в пакете leaflet
нет такого удобного автоматического раскрашивания по некоторой переменной, поэтому для решения такой задачи нужно сначала создать свою функцию раскрашивания. Это делается при помощи функций colorNumeric()
, colorFactor()
, colorBin()
или colorQuantile()
.
pal_cat <- colorFactor("Set3", domain = unwelcomed$collapsed_cause)
pal_cat(unwelcomed$collapsed_cause[1])
[1] "#D9D9D9"
Теперь в переменную pal_cat
записана функция, которая возварщает цвета в зависимости от значения. В качестве первого аргумента в фукнций colorNumeric()
, colorFactor()
, colorBin()
или colorQuantile()
отправляется палитра, которую пользователь может задать сам или использовать уже имеющуюся (их можно посмотреть при помощи команды RColorBrewer::display.brewer.all()
):
Теперь мы готовы сделать нашу первую осмысленную карту
unwelcomed %>%
filter(str_detect(date, "2014")) %>%
leaflet() %>%
addTiles() %>%
addCircles(lng = ~lon,
lat = ~lat,
label = ~total_death_missing, # пусть возникает подпись с количеством
color = ~pal_cat(collapsed_cause), # это обобщенная причина
opacity = 0.9,
popup = ~cause_of_death) %>% # а это конкретная причина, появляется при клике мышкой
addLegend(pal = pal_cat,
values = ~collapsed_cause,
title = "")
Вообще цветовая схема не очень сочетается с подложкой, так что можно поменять подложку при помощи функции addProviderTiles()
(галлерею подложек можно посмотреть вот здесь):
unwelcomed %>%
filter(str_detect(date, "2014")) %>%
leaflet() %>%
addProviderTiles("Stamen.TonerLite") %>%
addCircles(lng = ~lon,
lat = ~lat,
label = ~total_death_missing, # пусть возникает подпись с количеством
color = ~pal_cat(collapsed_cause), # это обобщенная причина
opacity = 0.9,
popup = ~cause_of_death) %>% # а это конкретная причина, появляется при клике мышкой
addLegend(pal = pal_cat,
values = ~collapsed_cause,
title = "")
Существует проект Карта ДТП, в котором собран датасет c дорожными происшествиями в России за некоторый временной промежуток. Визуализируйте все столкновения из датасета. Что можно увидеть на получившейся карте?
📋 список подсказок ➡
👁 Все забыто… Как скачать датасет? ➡
Надо использовать функциюread_csv()
из пакета tidyverse
.
👁 Карта получилась, но есть какие-то точки на Чукотке, которые не стой стороны… ➡
Да, это стандартная проблема с Чукоткой. Прибавьте к значениям долготы 360.👁 А как исправить значения на Чукотке? ➡
Ну нужно использовать функциюmutate()
, а в ней ifelse()
. Если значения меньше нуля — прибавляем 360, если больше — оставляем как есть.
10.3.2 Комбинация карт: leafsync
Карты, как и все объекты в R тоже можно записать в переменную:
unwelcomed %>%
filter(str_detect(date, "2014")) %>%
leaflet() %>%
addProviderTiles("Stamen.TonerLite") %>%
addCircles(lng = ~lon,
lat = ~lat,
label = ~total_death_missing, # пусть возникает подпись с количеством
color = ~pal_cat(collapsed_cause), # это обобщенная причина
opacity = 0.9,
popup = ~cause_of_death) %>% # а это конкретная причина, появляется при клике мышкой
addLegend(pal = pal_cat,
values = ~collapsed_cause,
title = "2014") ->
m_2014
Теперь если вызвать переменную m_2014
, появится карта, которую мы сделали. Но, что если мы хотим отобразить рядом карты 2014 года и 2015 года? Как сделать фасетизацию? К сожалению, функции для фасетизации в пакете не предусмотрена, но мы можем сделать ее самостоятельно. Для начала создадим вторую карту:
unwelcomed %>%
filter(str_detect(date, "2015")) %>%
leaflet() %>%
addProviderTiles("Stamen.TonerLite") %>%
addCircles(lng = ~lon,
lat = ~lat,
label = ~total_death_missing, # пусть возникает подпись с количеством
color = ~pal_cat(collapsed_cause), # это обобщенная причина
opacity = 0.9,
popup = ~cause_of_death) %>% # а это конкретная причина, появляется при клике мышкой
addLegend(pal = pal_cat,
values = ~collapsed_cause,
title = "2015") ->
m_2015
Включим библиотеку:
И теперь соединим две карты воедино:
10.3.3 Работа с .geojson
В данном разделе мы будем анализировать датасет, содержащий данные по всем странам мира.
countries <- jsonlite::read_json("https://github.com/agricolamz/DS_for_DH/raw/master/data/countries.geojson")
Обратите внимание, как уже говорилось в разделе @ref{lists}, так как jsonlite
конфликтует с одной из функций из tidyverse
, я не загружаю библиотеку полностью при помощи команды library(jsonlite)
, а обращаюсь к функциям пакета при помощи выражения jsonlite::...()
.
В загруженном датасете достаточно много переменных, мы попробуем проанализировать количество населения и уровень доходов.
countries$features %>%
map("properties") %>%
tibble(name = map_chr(., "name"),
pop_est = map_chr(., "pop_est"),
income = map_chr(., "income_grp")) %>%
select(-1) %>%
mutate(pop_est = as.double(pop_est),
income = as.factor(income)) ->
country_features
country_features
Еще одно преимущество формата .geojson
заключается в том, что его позволяет просматривать github (см. пример).
Самый простой способ визуализировать .geojson
это используя функцию addGeoJSON()
, которая в качестве аргумента принимает .geojson
файл.
Проблема этого подхода заключается в том, что файл .geojson
содержит в себе форматирование, поэтому если пользователь хочет поменять отображение объектов, необходимо добавить список style
к каждому узлу.
Во-первых, нужно добавить список style
в корень файла .geojson
. В результате, это изменит отображение всех списков:
countries$style = list(
weight = 1,
color = "#555555",
opacity = 1,
fillOpacity = 0.8)
leaflet() %>%
addGeoJSON(geojson = countries)
Во-вторых, следует создать палитры для раскрашивания. Это делается при помощи функций colorNumeric()
, colorFactor()
, colorBin()
или colorQuantile()
.
pal_num <- colorNumeric("Greens", domain = c(min(country_features$pop_est),
max(country_features$pop_est)))
pal_cat <- colorFactor("RdYlBu", domain = country_features$income)
Созданные переменные pal_num()
и pal_cat()
сами являются функциями и возвращают раскраску в зависимости от значения:
[1] "#F7FCF5"
[1] "#FDAE61"
В-третьих, нужно создать векторы с новыми цветами:
country_features %>%
mutate(pop_est_color = pal_num(pop_est),
income_color = pal_cat(income)) ->
country_features
country_features
В-четвертых, нужно присвоить каждому узлу свой список style
:
map(seq_along(countries$features), function(x){
countries$features[[x]]$properties$style <-
list(fillColor = country_features$income_color[x])
countries$features[[x]]
}) ->
countries$features
И последний, пятый шаг, это нарисовать получивший .geojson
:
leaflet() %>%
addGeoJSON(geojson = countries) %>%
addLegend(pal = pal_cat,
values = country_features$income,
title = "Income")
Повторите шаги 4 и 5 для числовой переменной (количество населения) из датасета.
Ссылки на литературу
Lovelace, R., J. Nowosad, and J. Muenchow. 2019. Geocomputation with R. CRC Press.