install.packages("leaflet")
10 Librería leaflet
10.1 Introducción
Leaflet es una de las bibliotecas de JavaScript de código abierto más populares para crear mapas interactivos. Instituciones y empresas de índole muy diversa, como la ONG Acción contra el hambre o Flickr, así como especialistas en SIG como OpenStreetMap, utilizan Leaflet para mostrar información geográfica en sus respectivos sitios web.
10.2 Características
- Panorámica/zoom interactivo.
- Componer mapas utilizando combinaciones arbitrarias de:
- Mosaicos de mapa
- Marcadores
- Polígonos
- Líneas
- Ventanas emergentes
- GeoJSON
- Mosaicos de mapa
- Crear mapas directamente desde la consola R o RStudio.
- Incrustar mapas en documentos knitr/R Markdown y aplicaciones Shiny.
- Representar fácilmente objetos espaciales de los paquetes sp o sf, o marcos de datos con columnas de latitud/longitud.
- Utilizar los límites del mapa y los eventos del mouse para impulsar la lógica Shiny.
- Mostrar mapas en proyecciones Mercator no esféricas.
10.3 Instalación
Para instalar el paquete leaflet
(Cheng et al., 2024), usamos la función habitual install.packages()
:
Si lo deseamos, podemos instalar la versión en desarrollo (no es necesario para esta práctica):
::install_github("rstudio/leaflet") devtools
Una vez instalada la librería, ya se puede cargar y usar desde R, en documentos R Markdown o Quarto (como este) o en aplicaciones Shiny como esta.
library(leaflet)
10.4 Funciones básicas
Antes de comenzar, cargamos algunas de las librerías habituales:
library(tidyverse)
library(sf)
library(classInt)
10.4.1 Mapa centrado en un lugar
Puesto que lo que queremos es representar algo en un mapa, lo mínimo que necesitamos es cargar el mapa y “pintar” algo en él.
Para cargar el mapa usamos la función addTiles()
.
Podemos pintar un punto, por ejemplo la ubicación de este edificio. Pero…
¿Cómo conseguimos las coordenadas de los marcadores que queremos mostrar?
leaflet() %>%
addTiles() %>%
addMarkers(lat=39.47776,
lng=-0.34374)
10.4.2 Markers
Si queremos que al hacer clic sobre el icono aparezca el texto deseado debemos incluir en la función addMarkers()
el parámetro popup
:
leaflet() %>%
addTiles() %>%
addMarkers(lat=39.47776,
lng=-0.34374,
popup="Facultat d'Economia - UV")
Podemos usar circulos en lugar de iconos, usando la función addCircleMarkers()
:
leaflet() %>%
addTiles() %>%
addCircleMarkers(lat=39.47776,
lng=-0.34374,
popup="Facultat d'Economia - UV",
color="red",
radius = 8,
stroke = 2,
opacity = 0.5)
10.4.3 Zoom
Podemos centrar el mapa en otro lugar, usando el parámetro zoom
de la función setView()
.
También debemos indicar las coordenadas que usaremos como centro del mapa.
El zoom por defecto es 15.
leaflet() %>%
addTiles() %>%
addMarkers(lat=39.47776,
lng=-0.34374,
popup="Facultat d'Economia - UV") %>%
setView(lat=40.41428,
lng=-3.72864,
zoom=5)
10.4.4 Mapa base
Si queremos usar otro mapa distinto al cargado por defecto, usaremos la función addProviderTiles()
.
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
addMarkers(lat=39.47776,
lng=-0.34374,
popup="Facultat d'Economia - UV")
Podemos seleccionar entre una amplia variedad de capas (Tiles). Hay más de 200 opciones disponibles. Aquí algunos ejemplos:
head(as.character(providers),20)
[1] "OpenStreetMap" "OpenStreetMap.Mapnik"
[3] "OpenStreetMap.DE" "OpenStreetMap.CH"
[5] "OpenStreetMap.France" "OpenStreetMap.HOT"
[7] "OpenStreetMap.BZH" "MapTilesAPI"
[9] "MapTilesAPI.OSMEnglish" "MapTilesAPI.OSMFrancais"
[11] "MapTilesAPI.OSMEspagnol" "OpenSeaMap"
[13] "OPNVKarte" "OpenTopoMap"
[15] "OpenRailwayMap" "OpenFireMap"
[17] "SafeCast" "Stadia"
[19] "Stadia.AlidadeSmooth" "Stadia.AlidadeSmoothDark"
En este enlace podemos visualizar todo el repertorio.
10.5 Funciones avanzadas
10.5.1 Puntos
Vamos a representar la ubicación de los centros educativos ubicados en la Comunitat Valenciana, utilizando el conjunto de datos que trabajamos (y mejoramos) en sesiones anteriores. El fichero con los datos, en formato GeoPackage, está disponible aquí.
<- sf::st_read("data/centres_educatius_bueno.gpkg", quiet=T) data_edu
Echemos un vistazo al dataset:
glimpse(data_edu)
Rows: 3,708
Columns: 34
$ codcen <chr> "03000047", "03000060", "03000096", "030…
$ dlibre <chr> "CEIP LA RAMBLA", "CENTRE PRIVAT LA MILA…
$ dgenerica_val <chr> "COL·LEGI D'EDUCACIÓ INFANTIL I PRIMÀRIA…
$ despecifica <chr> "LA RAMBLA", "LA MILAGROSA", "MIRADOR D'…
$ regimen <chr> "PÚBLICO", "CONCERTADO", "PÚBLICO", "PÚB…
$ direccion <chr> "AV. DE ALCOY, S/N", "CL. JUAN XXIII, 2"…
$ codpos <chr> "03698", "03698", "03569", "03340", "033…
$ municipio_oficial <chr> "Agost", "Agost", "Aigües", "Albatera", …
$ provincia_val <chr> "Alacant", "Alacant", "Alacant", "Alacan…
$ telef <chr> "966908135", "965691143", "966908140", "…
$ fax <chr> "0", "0", "0", "0", "0", "0", "0", "0", …
$ mail <chr> "03000047@edu.gva.es", "cdlamilagrosa@co…
$ codauto <chr> "10", "10", "10", "10", "10", "10", "10"…
$ iso2.ccaa.code <chr> "ES-VC", "ES-VC", "ES-VC", "ES-VC", "ES-…
$ nuts1.code <chr> "ES5", "ES5", "ES5", "ES5", "ES5", "ES5"…
$ nuts2.code <chr> "ES52", "ES52", "ES52", "ES52", "ES52", …
$ ine.ccaa.name <chr> "Comunitat Valenciana", "Comunitat Valen…
$ iso2.ccaa.name.es <chr> "Valenciana, Comunidad", "Valenciana, Co…
$ iso2.ccaa.name.ca <chr> "Valenciana, Comunitat", "Valenciana, Co…
$ iso2.ccaa.name.gl <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ iso2.ccaa.name.eu <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ nuts2.name <chr> "Comunidad Valenciana", "Comunidad Valen…
$ cldr.ccaa.name.en <chr> "Valencian Community", "Valencian Commun…
$ cldr.ccaa.name.es <chr> "Comunidad Valenciana", "Comunidad Valen…
$ cldr.ccaa.name.ca <chr> "País Valencià", "País Valencià", "País …
$ cldr.ccaa.name.ga <chr> "Comunidade Valenciana", "Comunidade Val…
$ cldr.ccaa.name.eu <chr> "Valentziako Erkidegoa", "Valentziako Er…
$ ccaa.shortname.en <chr> "Valencian Community", "Valencian Commun…
$ ccaa.shortname.es <chr> "Comunidad Valenciana", "Comunidad Valen…
$ ccaa.shortname.ca <chr> "País Valencià", "País Valencià", "País …
$ ccaa.shortname.ga <chr> "Comunidade Valenciana", "Comunidade Val…
$ ccaa.shortname.eu <chr> "Valentziako Erkidegoa", "Valentziako Er…
$ nuts1.name <chr> "ESTE", "ESTE", "ESTE", "ESTE", "ESTE", …
$ geom <POINT [°]> POINT (-0.635608 38.43839), POINT …
unique(data_edu$regimen)
[1] "PÚBLICO" "CONCERTADO" "PRIVADO"
Para poder representar datos espaciales (objetos sf
) con leaflet, tenemos que modificar el CRS a WGS84:
<- sf::st_transform(data_edu, 4326) data_edu
Representamos los datos en el mapa:
%>%
data_edu leaflet() %>%
addTiles() %>%
addCircles()
Creamos la paleta de colores para diferenciar los centros según regimen:
<- data_edu$regimen %>% unique()
tipos_regimen <- c('#66c2a5','#fc8d62','#8da0cb') # https://colorbrewer2.org/
colores <- leaflet::colorFactor(colores, domain = tipos_regimen) pal
Probemos nuevamente:
%>%
data_edu leaflet() %>%
addTiles() %>%
addCircles(color = ~pal(regimen))
Identifiquemos cada punto con el nombre de cada centro:
<- data_edu %>%
m leaflet() %>%
addTiles() %>%
addCircles(color = ~pal(regimen),
label = ~despecifica,
popup = ~dgenerica_val,
group = "Régimen")
m
Añadimos la leyenda:
<- m %>%
m addLegend(data = data_edu,
values = ~regimen,
position = "bottomright",
pal = pal,
title = "Régimen",
group = "Leyenda")
m
Añadimos control de capas:
%>%
m addLayersControl(overlayGroups = c("Régimen","Leyenda"),
options = layersControlOptions(collapsed = T))
Sería interesante poder seleccionar qué centros visualizar (en función de la variable regimen)…
<- list()
lista_regimen for (i in 1:length(tipos_regimen)) {
<- data_edu %>% filter(regimen == tipos_regimen[i])
lista_regimen[[i]]
}names(lista_regimen) <- tipos_regimen
<- leaflet() %>% addTiles()
map for (i in 1:length(lista_regimen)) {
<- map %>%
map addCircles(data = lista_regimen[[i]],
color = ~pal(regimen),
label = ~despecifica,
popup = ~dgenerica_val,
group = tipos_regimen[i])
}
<- map %>%
map addLegend(data = data_edu,
values = ~regimen,
position = "bottomright",
pal = pal,
title = "Régimen",
group = "Leyenda")
<- c("Leyenda", tipos_regimen)
grupos
<- map %>%
map2 addLayersControl(overlayGroups = grupos,
options = layersControlOptions(collapsed = T)) %>%
hideGroup("Leyenda")
map2
Otra forma de hacerlo:
<- map %>%
map3 addLayersControl(overlayGroups = "Leyenda",
baseGroups = tipos_regimen,
options = layersControlOptions(collapsed = T)) %>%
hideGroup("Leyenda")
map3
También podemos visualizar los puntos de forma agrupada:
%>%
data_edu leaflet() %>%
addProviderTiles("Esri.WorldImagery") %>%
addCircleMarkers(popup = ~paste(despecifica, "<br>", regimen),
color = "red",
stroke = 2,
radius = 8,
clusterOptions = markerClusterOptions())
10.5.2 Polígonos
Vamos a representar la distribución de la renta de los hogares. Para ello, hemos escogido la Renta bruta media por hogar, para cada sección censal de la provincia de Valencia. Los microdatos (para el año 2021) se han obtenido del INE. También puedes descargar el fichero directamente de aquí
<- readxl::read_xlsx("data/renta_bruta_media_hogar_sscc_val_prov.xlsx")
renta glimpse(renta)
Rows: 1,823
Columns: 2
$ CUSEC <chr> "4600101001", "4600201001", "4600301001", "460040100…
$ renta <chr> "29986", "31872", "33446", "33916", "35777", "43522"…
<- renta %>% mutate(renta = as.numeric(renta)) renta
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `renta = as.numeric(renta)`.
Caused by warning:
! NAs introducidos por coerción
<- renta %>% filter(!is.na(renta)) renta
La cartografía por secciones censales también la podemos obtener del INE (ver aquí).
- Descarga y lee la cartografía del INE para el año 2021.
- Selecciona las secciones censales que pertenecen a la provincia de Valencia.
- Cambia el CRS a EPSG:4326.
- Guarda el dataset resultante como sscc_2021_CPRO_46.gpkg.
Unimos cartografía y datos de renta:
<- sf::st_read("data/sscc_2021_CPRO_46.gpkg", quiet = T)
sscc_2021_val <- left_join(sscc_2021_val,renta, by="CUSEC") renta_sf
¿Qué tendríamos que hacer si la variable de unión tuviera distinto nombre en cada dataset?
Creamos intervalos:
<- classInt::classIntervals(var = renta_sf$renta, n=6, style = "quantile") breaks
Warning in classInt::classIntervals(var = renta_sf$renta, n = 6,
style = "quantile"): var has missing values, omitted in finding
classes
<- breaks$brks
breaks <- round(breaks, -2)
breaks
<- base::findInterval(x=pull(renta_sf, renta), vec = breaks, all.inside = T)
class <- renta_sf %>% mutate(class = class)
renta_sf <- tibble(class = 1:6,
labels min = scales::number(breaks[1:6], big.mark = ".", decimal.mark = ","),
max = scales::number(breaks[2:7], big.mark = ".", decimal.mark = ","),
interval = paste0(min, " - ", max))
<- inner_join(renta_sf, labels, by = "class")
renta_sf <- mutate(renta_sf, interval = factor(interval, levels = labels$interval)) renta_sf
En ocasiones, puede ser útil reducir la calidad de la cartografía (número de puntos que conforman los polígonos):
#install.packages("rmapshaper")
#library(rmapshaper)
<- rmapshaper::ms_simplify(renta_sf) renta_sf
Creamos la paleta de colores:
#install.packages("RColorBrewer")
#library(RColorBrewer)
#RColorBrewer::display.brewer.all()
<- RColorBrewer::brewer.pal(n=6, name = "YlGn")
palette <- tibble(clases = labels$interval, palette)
colors <- mutate(colors, clases = factor(clases, levels = labels$interval))
colors <- mutate(renta_sf, clases = interval)
renta_sf <- inner_join(renta_sf, colors, by = "clases") renta_sf
Representamos los datos en el mapa, con algunas funcionalidades extra que ofrece el paquete leaflet.extras
(Gatscha et al., 2024):
#install.packages("leaflet.extras")
#library(leaflet.extras)
::leaflet() %>%
leaflet::addTiles() %>%
leaflet::addProviderTiles(provider = providers$CartoDB.Positron, group = "CartoDB") %>%
leaflet::addPolygons(data = renta_sf,
leaflet#stroke = T,
color = "transparent",
fillColor = renta_sf$palette,
fillOpacity = 0.5,
smoothFactor = 0.5,
popup = paste0("Renta bruta media por hogar: ", renta_sf$renta, " €", "<br>",
"Municipio: ", renta_sf$NMUN)) %>%
::addLegend("topright",
leafletcolors = colors$palette,
labels = colors$clases,
title = "Renta bruta media por hogar") %>%
::addResetMapButton() %>%
leaflet.extras::addSearchOSM() %>%
leaflet.extras::addControlGPS() %>%
leaflet.extras::addMiniMap(tiles = "Esri.WorldStreetMap", position = "bottomleft") leaflet