Mapas no R parte I: explorando Moscou
Moscou é famosa por diversos motivos: sua história, milenar, que se inicia nos tempos da Rus’ de Kiev; o fato de ser o berço da Revolução Russa; suas largas ruas, algumas com oito faixas, para tanques de guerra poderem passar; e suas belíssimas estações de metrô, que são verdadeiras obras de arte. O metrô de Moscou, além de servir como ponto de abrigo em casos de guerra nuclear.
Suas 263 estações, ao longo de 397.3km, são profundas, algumas com enormes portas de chumbo, e guardam obras de arte, como pinturas, estátuas, e a própria arquitetura em si. Nenhuma estação é igual a outra, e elas são puros reflexos da arte de seu tempo: enquanto as primeiras estações, inauguradas em 1935, como a Park Kultury possuem uma estrutura mais rígida, fruto de um momento histórico onde o stalinismo estava combinado com um país ainda em lento crescimento. Percebemos em estações posteriores, como a Kievskaya, inagurada em 1954, em um período de maior riqueza, adota uma arte mais suave, denominada “classicismo socialista”. Já em 2018, a estação Savyolovskaya, na linha 11, é completamente moderna.
O metrô de Moscou transporta em média 7 milhões de pessoas por dia. Pressupõe-se, então, que o alcance dele por toda a região de Moscou é enorme, e é isso que verificaremos hoje: como estão organizados os sistemas de trem e metrô do distrito de Moscou, que possui status de Estado, na Federação Russa.
Para essa análise, utilizaremos no R os pacotes leaflet, que nos permite gerar mapas interativos e navegáveis, osmdata, para baixar os mapas do OpenStreetMap, que é uma plataforma aberta de mapas construída pela comunidade, e é claro, o tidyverse, para manipular os bancos que utilizaremos.
O primeiro passo de sempre é carregar os pacotes. Depois, baixaremos as informações do OpenStreetMap através da função opq()
, que nos permite fazer buscas dentro do sistema por nomes, coordenadas, e muito mais. Todavia, não queremos simplesmente o mapa, queremos informações do mapa, como as linhas de metrô, trem, e estações. Logo, utilizaremos a função add_osm_feature()
, que baixa as tags do mapa que escolhemos (mais informações sobre as tags aqui). Podemos baixar linhas de metrô, calçadas, prédios, farmácias, árvores e até mesmo cercas e muros. Em seguida, transformaremos para o formato sf, Spatial Feature, um formato de mapa que é muito bem trabalhado no R. Existem outros formatos, como geojson, sp, topojson, cada um com suas vantagens e desvantagens. Eu prefiro trabalhar com o sf por estar mais acostumado, mas dominar todos os outros também é importante.
Bom, vamos lá:
pacman::p_load(osmdata, tidyverse, leaflet, sf, leaflet.extras)
moscou_metro <- opq("Moscow", timeout = 240, memsize = 1073741824) %>%
add_osm_feature(key = "railway", value = "subway") %>%
osmdata_sf()
moscou_metrostat <- opq("Moscow", timeout = 240, memsize = 1073741824) %>%
add_osm_feature(key = "station", value = "subway") %>%
osmdata_sf()
moscou_city <- opq("Moscow", timeout = 240, memsize = 1073741824) %>%
add_osm_feature(key = "name:pt", value = "Moscou") %>%
osmdata_sf()
moscou_trem <- opq("Moscow", timeout = 240, memsize = 1073741824) %>%
add_osm_feature(key = "railway", value = "rail") %>%
osmdata_sf()
O que fiz acima, depois de ter carregado os pacotes: procurei por “Moscow”, defini o timeout em 120 e o tamanho da memória em 1gb, em bytes. A API do OSM é um pouco travada às vezes, e o timeout pode ser um problema recorrente (timeout é quando o servidor demora muito tempo para responder). Ao aumentar o tempo, a chance do timeout é diminuída, e ao aumentar o tamanho da memória, ele permite baixar dados que podem ser mais pesados.
Em moscou_metro
eu procurei por todas as linhas de metrô, usando a chave-valor railway:subway. Já em moscou_metrostat
, station:subway, para obter todos os pontos de estações. Em moscou_city
, peguei os limites da cidade, a partir do nome em português name:pt:Moscou, e em moscou_trem
, as linhas de trem.
Vamos observar como se estrutura um banco de dados com informações de mapa:
moscou_metro
## Object of class 'osmdata' with:
## $bbox : 55.4913076,37.290502,55.9577717,37.9674277
## $overpass_call : The call submitted to the overpass API
## $meta : metadata including timestamp and version numbers
## $osm_points : 'sf' Simple Features Collection with 14234 points
## $osm_lines : 'sf' Simple Features Collection with 2026 linestrings
## $osm_polygons : NULL
## $osm_multilines : NULL
## $osm_multipolygons : NULL
# O que nos interessa aqui são os elementos iniciados por osm_, que indicam pontos,
# linhas, polígonos, multilinhas e multipolígonos (conjuntos de linhas e polígonos).
glimpse(moscou_metro$osm_lines)
## Rows: 2,026
## Columns: 30
## $ osm_id <chr> "22476058", "22745717", "23387050", "23387…
## $ name <chr> "\u0424\u0438\u043b\u0451\u0432\u0441\u043…
## $ bridge <chr> NA, NA, NA, NA, "yes", NA, NA, NA, NA, NA,…
## $ colour <chr> "lightblue", "blue", "lightblue", "lightbl…
## $ construction <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ covered <chr> NA, NA, NA, NA, NA, "yes", NA, NA, NA, NA,…
## $ cutting <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ description <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ electrified <chr> "rail", "rail", "rail", "rail", "rail", "r…
## $ frequency <chr> "0", "0", "0", "0", "0", "0", "0", "0", "0…
## $ gauge <chr> "1520", "1520", "1520", "1520", "1520", "1…
## $ indoor <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ layer <chr> NA, "-2", "-1", NA, "1", NA, NA, "-1", "-1…
## $ level <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ location <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ name.be <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, "\u042…
## $ name.de <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ name.ru <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, "\u042…
## $ official_name <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ old_name <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ oneway <chr> "no", NA, "yes", "yes", NA, NA, "yes", "ye…
## $ public_transport <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ railway <chr> "subway", "subway", "subway", "subway", "s…
## $ railway.preferred_direction <chr> NA, "both", NA, NA, "forward", "forward", …
## $ ref <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ service <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ subway <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ tunnel <chr> "no", "yes", "yes", "no", "no", "no", "no"…
## $ voltage <chr> "750", "750", "750", "750", "750", "750", …
## $ geometry <LINESTRING [°]> LINESTRING (37.45 55.7317, ...,…
# Aqui percebemos que o banco tem algumas variáveis de informação do mapa,
# como bridge, construction, tunnel...
# Antes de avançarmos, vamos pegar as localizações centrais do mapa, para
# que possamos utilizar mais frente...
#
gps_coords <- moscou_city$bbox %>% str_split(",") %>% # Pegar as localizações
unlist() %>% # tirar de lista
as.numeric() # transformar em número
lng_coord <- (gps_coords[2] + gps_coords[4])/2 # Média das longitudes
lat_coord <- (gps_coords[1] + gps_coords[3])/2 # Média das latitudes
Todos os 4 bancos que baixamos tem essa mesma estrutura, só mudando as variáveis. No caso, precisamos selecionar de cada um deles as variáveis name
e geometry
. Mas antes disso, precisamos pensar de onde retirar: linhas, polígonos ou pontos?
Para as linhas de trem e metrô, o ideal é usar
osm_lines
, por se tratarem de retas ao longo do plano;Para as fronteiras da cidade de Moscou, é bom utilizar
osm_multipolygons
, que é um conjunto de polígonos, ou seja, com preenchimento;Para as estações, podemos utilizar
osm_points
para termos indicações de onde estão as estações. Os pontos geralmente ficam no ponto médio do polígono da plataforma.
Façamos então agora o tratamento desses dados:
moscou_metro <- moscou_metro$osm_lines %>% # utilizando somente osm_lines
select(name) %>% # selecionando a variável name
mutate(name = fct_drop(name), # removendo os nomes não utilizados
name = fct_explicit_na(name)) %>% # tornando os NA como "missing"
group_by(name) %>% # agrupando por nome, pro caso de haverem linhas paralelas com o mesmo nome
summarise() # sumarizando para unir tais linhas
moscou_metrostat <- moscou_metrostat$osm_points %>%
select(name) %>%
mutate(name = fct_drop(name),
name = fct_explicit_na(name))
moscou_city <- moscou_city$osm_multipolygons %>%
select(name) %>%
mutate(name = fct_drop(name),
name = fct_explicit_na(name)) %>%
group_by(name) %>%
summarise()
moscou_trem <- moscou_trem$osm_lines %>%
select(name) %>%
mutate(name = fct_drop(name),
name = fct_explicit_na(name)) %>%
group_by(name) %>%
summarise()
Antes de plotarmos os leaflets, vamos testar se pelo menos alguns dos mapas vieram corretamente, utilizando o ggplot()
com a função geom_sf()
, própria para mapas. Lembrando: a API do OSM pode ser problemática às vezes, por isso testar e verificar os dados a todo instante é importante.
ggplot()+
geom_sf(data = moscou_city, color = "black", fill = "ivory", size = 0.25)+
geom_sf(data = moscou_metro, color = "black")+
geom_sf(data = moscou_metrostat, color = "lightseagreen", size = 0.9)+
hrbrthemes::theme_ipsum_tw()+
labs(title = "Metrô de Moscou",
subtitle = "Linhas e estações",
caption = "Fonte: OpenStreetMap.org")
Os mapas estão funcionando! A primeira coisa que salta aos olhos é a falta de uma linha do metrô, a linha 14, “Circular-Central”, que é circular como a Koltsevaya, visível no centro do mapa, mas abrange áreas mais externas. Todavia, o fato dela não aparecer aí é mais uma questão burocrática que um erro: apesar de administrada pelo metrô de Moscou, utilizar a tarifa do metrô e ser integrada ao metrô, a linha 14 é na superfície, e em muitos pontos, suspensa. Logo, pelas diretrizes do OpenStreetMap, ela é considerada uma linha de trem.
Agora, vamos entrar de fato no Leaflet e gerar mapas interativos e navegáveis. Antes, é preciso entender um pouco da estrutura do comando, para podermos simplificá-lo ao máximo. O leaflet, assim como o ggplot2, é iniciado por uma função principal, no caso, leaflet()
. Tal qual a função ggplot()
, essa função só abre o viewer do R, não gerando nada. A camada de mapa só é adicionada com a função addTiles()
, onde um mapa mundi é gerado. A estética do mapa pode ser dada pela função addProviderTiles()
, onde podemos utilizar mapas de satélites, aquarelas, preto-e-branco, topográficos, dentre outros. Tais funções são obrigatórias (salvo a addProviderTiles()
). Por isso, para tornar o código enxuto, vamos criar objetos com essas funções para não precisarmos repetí-las a todo momento:
leaflet() # Nada!
leaflet() %>%
addTiles() # Mapa Mundi
leaflet() %>%
addTiles() %>%
addProviderTiles(providers$Stamen.TonerBackground, group = "TonerBackground (Default)") # Mapa Mundi com estética Toner, da Stamen
leafs.basic <- leaflet() %>%
addTiles() %>%
addProviderTiles(providers$Stamen.TonerBackground)
O próximo passo seria adicionar os dados dos mapas. As funções para isso são as addPolylines()
e addPolygons()
, para linhas/multilinhas e polígonos/multipolígonos, respectivamente. Para pontos, utilizamos addCircleMarkers()
.
leafs.basic %>%
addPolygons(data = moscou_city,
opacity = 0.5, color = "black",
fillOpacity = 0.08, weight = 1) %>%
addPolylines(data = moscou_metro, # banco
label = ~name, # nome das linhas
color = "lightseagreen", # cor da linha
opacity = 2, weight = 4, # opacidade e grossura da linha
group = "Linhas de Metrô", # nome do grupo
highlight = highlightOptions(color = "red")) %>% # cor de highlight
addPolylines(data = moscou_trem,
label = ~name,
color = "coral",
opacity = 2, weight = 3,
group = "Linhas de Trem",
highlight = highlightOptions(color = "red")) %>%
addCircleMarkers(data = moscou_metrostat,
label = ~name,
radius = 0.2,
opacity = 1,
color = "midnightblue",
group = "Estações de Metrô")
Pronto, agora já temos um mapa funcional. Nele, conseguimos descobrir o nome de algumas linhas e estações ao passarmos o mouse. O nome que aparece veio direto das variáveis name
, que guardamos acima, e que utilizamos com o argumento label
, dentro de addPolylines()
. Os mapas estão bons, mas podem ficar melhores: podemos adicionar camadas que podem ser marcadas ou desmarcadas, adicionar função de GPS, pesquisa, e definir as coordenadas centrais onde o mapa irá abrir, além do nível de zoom.
leafs.basic %>%
addPolygons(data = moscou_city,
opacity = 0.5, color = "black",
fillOpacity = 0.08, weight = 1) %>%
addPolylines(data = moscou_metro, # banco
label = ~name, # nome das linhas
color = "lightseagreen", # cor da linha
opacity = 2, weight = 4, # opacidade e grossura da linha
group = "Linhas de Metrô", # nome do grupo
highlight = highlightOptions(color = "red")) %>% # cor de highlight
addPolylines(data = moscou_trem,
label = ~name,
color = "coral",
opacity = 2, weight = 3,
group = "Linhas de Trem",
highlight = highlightOptions(color = "red")) %>%
addCircleMarkers(data = moscou_metrostat,
label = ~name,
radius = 0.2,
opacity = 1,
color = "midnightblue",
group = "Estações de Metrô") %>%
addProviderTiles(providers$Esri.WorldStreetMap, group = "ESRI WorldStreetMap") %>%
addProviderTiles(providers$Esri.WorldImagery, group = "ESRI WorldImagery") %>%
addProviderTiles(providers$CartoDB.Positron, group = "CartoDB Positron") %>%
addProviderTiles(providers$MtbMap, group = "MtbMap") %>%
addLayersControl(baseGroups = c("TonerBackground (Default)",
"ESRI WorldStreetMap",
"ESRI WorldImagery",
"CartoDB Positron",
"MtbMap"),
overlayGroups = c("Linhas de Metrô", "Estações de Metrô", "Linhas de Trem"),
options = layersControlOptions(collapsed = F),
position = "bottomright") %>%
addSearchOSM() %>%
addControlGPS() %>%
setView(zoom = 12,
lat = lat_coord+0.02,
lng = lng_coord)
Como imaginado antes, a linha Circular-Central está categorizada no OpenStreetMap como trem, e não apareceu, portanto, nas linhas de metrô.
Prédios
O OSM é riquíssimo em informações, e até mesmo prédios estão listados na base de dados a partir de sua categoria, ou seja, podemos procurar lojas, bancos, escolas, garagens, etc. A seguir, um exemplo, já completo, com um mapa da cidade exibindo as escolas, as universidades, as bibliotecas e os prédios de governo.
moscou_escolas <- opq("Moscow", timeout = 120, memsize = 1073741824) %>%
add_osm_feature(key = "amenity", value = "school") %>%
osmdata_sf()
moscou_biblio <- opq("Moscow", timeout = 120, memsize = 1073741824) %>%
add_osm_feature(key = "amenity", value = "library") %>%
osmdata_sf()
moscou_univ <- opq("Moscow", timeout = 120, memsize = 1073741824) %>%
add_osm_feature(key = "amenity", value = "university") %>%
osmdata_sf()
moscou_gov <- opq("Moscow", timeout = 1200, memsize = 1073741824) %>%
add_osm_feature(key = "office", value = "government") %>%
osmdata_sf()
moscou_escolas <- moscou_escolas$osm_polygons %>%
select(name) %>%
mutate(name = fct_drop(name),
name = fct_explicit_na(name)) %>%
group_by(name) %>%
summarise()
moscou_univ <- moscou_univ$osm_polygons %>%
select(name) %>%
mutate(name = fct_drop(name),
name = fct_explicit_na(name)) %>%
group_by(name) %>%
summarise()
moscou_biblio <- moscou_biblio$osm_polygons %>%
select(name) %>%
mutate(name = fct_drop(name),
name = fct_explicit_na(name)) %>%
group_by(name) %>%
summarise()
moscou_gov1 <- moscou_gov$osm_polygons %>%
select(name) %>%
mutate(name = fct_drop(name),
name = fct_explicit_na(name)) %>%
group_by(name) %>%
summarise()
moscou_gov2 <- moscou_gov$osm_multipolygons %>%
select(name) %>%
mutate(name = fct_drop(name),
name = fct_explicit_na(name))
leafs.basic %>%
addPolygons(data = moscou_escolas, opacity = 1,
fillColor = "hotpink", color = "hotpink",
label = ~name, weight = 2, group = "Escolas") %>%
addPolygons(data = moscou_univ, opacity = 1,
fillColor = "coral", color = "coral",
label = ~name, weight = 2, group = "Universidades") %>%
addPolygons(data = moscou_biblio, opacity = 1,
fillColor = "darkcyan", color = "darkcyan",
label = ~name, weight = 3, group = "Bibliotecas") %>%
addPolygons(data = moscou_gov1, label = ~name, opacity = 1,
fillColor = "darkviolet", color = "darkviolet",
weight = 2, group = "Governo") %>%
addPolygons(data = moscou_gov2, label = ~name, opacity = 1,
fillColor = "darkviolet", color = "darkviolet",
weight = 2, group = "Governo") %>%
addLayersControl(overlayGroups = c("Escolas", "Universidades",
"Bibliotecas", "Governo"),
options = layersControlOptions(collapsed = F),
position = "bottomright") %>%
addSearchOSM() %>%
setView(zoom = 13,
lat = lat_coord+0.02,
lng = lng_coord) %>%
addControlGPS()
Mas e o Kremlin? Se repararmos bem, o Kremlin, que deveria aparecer na categoria de governo, não aparece. Isso se dá pelo seguinte motivo: antes de ser categorizado como prédio do governo, ele é categorizado como um castelo. Logo, não entrou no banco quando fizemos o download de tudo.
Se você encontrou dificuldade para baixar os dados de mapa usando o OSMData, os arquivos estão disponíveis aqui!
Bom, por hoje é só! Futuramente, farei uma parte 2, na qual somaremos informações de outro banco ao mapa, podendo construir bancos mais úteis com informações que não estão listadas no OSM e que foram preparadas por nós. Espero que tenham gostado! Qualquer dúvida, correção ou sugestão pode ser enviada para meu email matheus.pestana@iesp.uerj.br .