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)
## 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 .

Mateus Cavalcanti Pestana
Mateus Cavalcanti Pestana
Doutorando e Mestre em Ciência Política

Interessado em ciência de dados, ciência política, política russa, impressão 3D, redes neurais e aprendizado de máquina.

Relacionados