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.

A estação Park Kultury

Acima, a estação Kievskaya

A estação Savyolovskaya

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)
## Observations: 2,026
## Variables: 30
## $ osm_id                      <chr> "22476058", "22745717", "23387050", …
## $ name                        <chr> "Филёвская линия", NA, "Филёвская ли…
## $ bridge                      <chr> NA, NA, NA, NA, "yes", NA, NA, NA, N…
## $ colour                      <chr> "lightblue", "blue", "lightblue", "l…
## $ construction                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ covered                     <chr> NA, NA, NA, NA, NA, "yes", NA, NA, N…
## $ cutting                     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ description                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ electrified                 <chr> "rail", "rail", "rail", "rail", "rai…
## $ frequency                   <chr> "0", "0", "0", "0", "0", "0", "0", "…
## $ gauge                       <chr> "1520", "1520", "1520", "1520", "152…
## $ indoor                      <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ layer                       <chr> NA, "-2", "-1", NA, "1", NA, NA, "-1…
## $ level                       <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ location                    <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ name.be                     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ name.de                     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ name.ru                     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ official_name               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ old_name                    <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ oneway                      <chr> "no", NA, "yes", "yes", NA, NA, "yes…
## $ public_transport            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ railway                     <chr> "subway", "subway", "subway", "subwa…
## $ railway.preferred_direction <chr> NA, "both", NA, NA, "forward", "forw…
## $ ref                         <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ service                     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ subway                      <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ tunnel                      <chr> "no", "yes", "yes", "no", "no", "no"…
## $ voltage                     <chr> "750", "750", "750", "750", "750", "…
## $ geometry                    <LINESTRING [°]> LINESTRING (37.45002 55.7…
# 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.data <- 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ô")

leafs.data