Hoje acordei curioso para saber qual era a capital mais distante de Brasília. Essa não é uma questão tão trivial quanto parece, pois, como a Terra é esférica (apesar de todas as controvérsias), podemos chegar em um ponto por mais de um caminho. Por isso, resolvi calcular todas as distâncias e montar um mapinha!
Encontrando as coordenadas geográficas
O primeiro passo para essa aventura foi encontrar as coordenadas geográficas das capitais de todo o mundo. Encontrei nesse post uma forma de fazer isso em R. O post estava desatualizado, então resolvi reescrevê-lo da maneira tidy.
Começamos usando o maravilhoso pacote {httr} para obter o HTML da página que contém os dados. Precisei modificar o User-Agent pois, sem ele, a requisição retornava com código 406.
## Download a partir do site
r_capitals <- httr::GET(
  "https://lab.lmnixon.org/4th/worldcapitals.html",
  httr::user_agent("Mozilla/5.0 (X11; Linux x86_64)")
)Em seguida, usamos nosso queridinho {xml2} para encontrar a tabela, e o não tão queridinho {rvest} para transformar essa tabela (temos uma discussão sobre isso aqui). Os pacotes {tibble} e {janitor} foram usados para deixar a tabela formatada.
## Parse do resultado do site
da_countries_raw <- r_capitals %>% 
  xml2::read_html() %>% 
  xml2::xml_find_first("//table") %>% 
  rvest::html_table(header = TRUE) %>% 
  tibble::as_tibble() %>% 
  janitor::clean_names()Arrumando os dados
Como bom faxineiro de dados, eu não poderia deixar se mostrar a parte mais divertida da ciência de dados: organizar os dados brutos! Primeiro, as coordenadas de latitude e longitude estavam em texto e, ao invés de mostrar valores positivos e negativos, mostrava os valores N (norte), S (sul), E (leste), W (oeste). Além disso, a latitude e longitude de Jerusalém (Israel) estava incorreta.
library(tidyverse)
da_countries_tidy <- da_countries_raw %>% 
  filter(country != "") %>% 
  # transforma (N,S) (E,W) em (1,-1), (1,-1)
  mutate(
    lat_num = str_detect(latitude, "N") * 2 - 1,
    lng_num = str_detect(longitude, "E") * 2 - 1
  ) %>% 
  # transforma em numérico
  mutate(
    across(c(latitude, longitude), parse_number),
    lat = latitude * lat_num,
    lng = longitude * lng_num
  ) %>% 
  # arruma Israel
  mutate(
    lat = if_else(country == "Israel", 31.7683, lat),
    lng = if_else(country == "Israel", 35.2137, lng)
  ) %>% 
  select(country, capital, lat, lng)Transformando os dados
Com os dados arrumados em mãos, calculei as distâncias através da distância geodésica1, usando latitude e longitude como base e o maravilhoso pacote {sf}. São duas funções principais: a sf::st_point() cria um objeto especial do tipo ponto, e a sf::st_distance() calcula a distância entre dois pontos. Utilizamos map2() e map() do pacote {purrr} para fazer aplicar essas operações em todos os países. No final, temos a base ordenada pelas distâncias. As distâncias são calculadas em metros, que transformamos para quilômetros.
da_countries <- da_countries_tidy %>% 
  # cria pontos com base em lat lng e sf::st_point()
  # sf::st_sfc() transforma a lista num objeto POINT do {sf}
  # crs = 4326 serve para o {sf} saber que
  #   são coordenadas no planeta Terra.
  mutate(pt = sf::st_sfc(
    map2(lng, lat, ~sf::st_point(c(.x, .y, 1))),
    crs = 4326
  )) %>%
  mutate(
    across(c(lat, lng), list(br = ~.x[country == "Brazil"])),
    pt_br = sf::st_sfc(
      list(sf::st_point(c(lng_br[1], lat_br[1], 1))), 
      crs = 4326
    )
  ) %>% 
  mutate(
    dist_br = sf::st_distance(pt, pt_br, by_element = TRUE),
    dist_br = as.numeric(dist_br / 1000)
  ) %>% 
  # ordena a base pelas distâncias
  arrange(dist_br)Visualizando
As capitais mais próximas estão na Tabela 1. Sem muitas surpresas aqui: como Brasília fica na região central do país, a capital mais próxima é a do Paraguai, seguida por outros países da América do Sul.
| País | Capital | Distância (km) | 
|---|---|---|
| Paraguay | Asuncion | 1473 | 
| Bolivia | La Paz (administrative) / Sucre (legislative) | 2202 | 
| Uruguay | Montevideo | 2276 | 
| French Guiana | Cayenne | 2326 | 
| Suriname | Paramaribo | 2464 | 
| Argentina | Buenos Aires | 2618 | 
| Guyana | Georgetown | 2695 | 
| Chile | Santiago | 3028 | 
| Peru | Lima | 3206 | 
| Barbados | Bridgetown | 3409 | 
As coisas ficam mais interessantes quando visualizamos as capitais mais distantes, na Tabela 2. E temos nosso resultado: Koror (Palau) é a capital mais distante da capital, Brasília, seguida por Manila (Filipinas) e Saipan (Ilhas Mariana do Norte).
| País | Capital | Distância (km) | 
|---|---|---|
| Palau | Koror | 19069 | 
| Philippines | Manila | 18801 | 
| Northern Mariana Islands | Saipan | 18639 | 
| Macao, China | Macau | 17887 | 
| Brunei Darussalam | Bandar Seri Begawan | 17765 | 
| Republic of Korea | Seoul | 17517 | 
| North Korea | Pyongyang | 17296 | 
| East Timor | Dili | 17264 | 
| Viet Nam | Hanoi | 17126 | 
| Micronesia (Federated States of) | Palikir | 17066 | 
Mas será mesmo? Vamos usar o pacote {leaflet} para visualizar:
library(leaflet)
# Cria as labels dos popups
make_label <- function(pais, capital) {
  txt <- stringr::str_glue(
    "<b>País</b>: {pais}<br>",
    "<b>Capital</b>: {capital}"
  )
  htmltools::HTML(txt)
}
p_leaflet <- da_countries %>% 
  mutate(lab = map2(country, capital, make_label)) %>% 
  # cria mapa
  leaflet() %>% 
  # adiciona a casquinha
  addTiles() %>% 
  # adiciona os pontos
  addMarkers(
    clusterOptions = markerClusterOptions(), 
    lat = ~lat, lng = ~lng, popup = ~lab
  )
p_leaflet
Acesse o mapa dinâmico neste link.
Olhando o mapa (e considerando que a terra é esférica), parece mesmo que esses países estão bem longe, mesmo tentando acessar pelo leste ou pelo oeste.
E, já que a terra é esférica, que tal criar um mapa 3D? Fiz isso usando o {plotly}, com base no tutorial disponível neste link.
library(plotly)
p_plotly <- plot_ly(height = 1000) %>%
  # adiciona o mapa mundi
  add_sf(
    data = world, 
    x = ~coord_x(x, y),
    y = ~coord_y(x, y),
    z = ~coord_z(y),
    color = I("gray80"), 
    size = I(2),
    hoverinfo = "none"
  ) %>% 
  # adiciona as linhas
  add_sf(
    data = da_lines_sf,
    name = "linhas",
    x = ~coord_x(x, y),
    y = ~coord_y(x, y),
    z = ~coord_z(y),
    color = ~dist_br,
    size = I(3),
    text = ~label_plotly(country, capital, dist_br),
    hoverinfo = "text"
  ) %>% 
  layout(showlegend = FALSE)
p_plotly
Acesse o mapa dinâmico neste link.
Wrap-up
Nesse post vimos como usar ferramentas do {tidyverse}, {sf} para transformação de dados para mapas, além de utilizar o {leaflet} e o {plotly} para visualizações interativas.
O código completo para construir as visualizações do zero está neste link.
É isso pessoal. Happy coding ;)
- Karney, Charles FF, 2013, Algorithms for geodesics, Journal of Geodesy 87(1), 43–55.↩︎ 


