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, Schloerke, et al., 2025) usaremos, como es habitual, la función install.packages():
Si lo deseamos (no es necesario para esta práctica), podemos instalar la versión en desarrollo (puede coincidir con la versión disponible en CRAN), disponible en github:
devtools::install_github("rstudio/leaflet")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)
packageVersion("leaflet")[1] '2.2.3'
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 (capa de fondo) 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 (aulario sur). 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 = "Aulari Sud - Campus dels Tarongers")Podemos usar círculos en lugar de iconos, usando la función addCircleMarkers():
leaflet() %>%
addTiles() %>%
addCircleMarkers(lat = 39.47776, lng = -0.34374,
popup = "Aulari Sud - Campus dels Tarongers",
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 = "Aulari Sud - Campus dels Tarongers") %>%
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 = "Aulari Sud - Campus dels Tarongers")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 comentamos en sesiones anteriores. El fichero con los datos, en formato GeoPackage, está disponible aquí.
data_edu <- sf::st_read("data/centres_educatius_bueno.gpkg", quiet=T)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"
data_edu %>%
leaflet() %>%
addTiles() %>%
addCircles()Warning: sf layer has inconsistent datum (+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs).
Need '+proj=longlat +datum=WGS84'
¿Qué significa este aviso? ¿Qué deberíamos hacer?
Para poder representar datos espaciales (objetos sf) con leaflet, tenemos que modificar el CRS a WGS84:
data_edu <- sf::st_transform(data_edu, 4326)Representamos nuevamente los datos en el mapa:
data_edu %>%
leaflet() %>%
addTiles() %>%
addCircles()Creamos la paleta de colores para diferenciar los centros en función de la variable regimen:
tipos_regimen <- data_edu$regimen %>% unique()
colores <- c('#66c2a5','#fc8d62','#8da0cb') # https://colorbrewer2.org/
pal <- leaflet::colorFactor(colores, domain = tipos_regimen)Probemos nuevamente:
data_edu %>%
leaflet() %>%
addTiles() %>%
addCircles(color = ~pal(regimen))Identifiquemos cada punto con el nombre de cada centro:
m <- data_edu %>%
leaflet() %>%
addTiles() %>%
addCircles(color = ~pal(regimen),
label = ~despecifica,
popup = ~dgenerica_val,
group = "Régimen")
class(m)[1] "leaflet" "htmlwidget"
mAñadimos la leyenda:
m <- m %>%
addLegend(data = data_edu,
values = ~regimen,
position = "bottomright",
pal = pal,
title = "Régimen",
group = "Leyenda")
mAñ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)…
lista_regimen <- list()
for (i in 1:length(tipos_regimen)) {
lista_regimen[[i]] <- data_edu %>% filter(regimen == tipos_regimen[i])
}
names(lista_regimen) <- tipos_regimen
map <- leaflet() %>% addTiles()
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")
grupos <- c("Leyenda", tipos_regimen)
map2 <- map %>%
addLayersControl(overlayGroups = grupos,
options = layersControlOptions(collapsed = T)) %>%
hideGroup("Leyenda")
map2Otra forma de hacerlo:
map3 <- map %>%
addLayersControl(overlayGroups = "Leyenda",
baseGroups = tipos_regimen,
options = layersControlOptions(collapsed = T)) %>%
hideGroup("Leyenda")
map3Tambié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í
rnt_val21 <- readxl::read_xlsx("data/renta_bruta_media_hogar_sscc_val_prov.xlsx")
head(rnt_val21)glimpse(rnt_val21)Rows: 1,823
Columns: 2
$ CUSEC <chr> "4600101001", "4600201001", "4600301001", "460040100…
$ renta <chr> "29986", "31872", "33446", "33916", "35777", "43522"…
rnt_val21 <- rnt_val21 %>% mutate(renta = as.numeric(renta))Warning: There was 1 warning in `mutate()`.
ℹ In argument: `renta = as.numeric(renta)`.
Caused by warning:
! NAs introducidos por coerción
rnt_val21 <- rnt_val21 %>% filter(!is.na(renta))
summary(rnt_val21) CUSEC renta
Length:1809 Min. : 16299
Class :character 1st Qu.: 31438
Mode :character Median : 35431
Mean : 38987
3rd Qu.: 41511
Max. :131719
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.
¿Coincide el número de observaciones de ambos datasets? ¿Qué secciones censales difieren?
Unimos cartografía y datos de renta:
sscc_2021_val <- sf::st_read("data/sscc_2021_CPRO_46.gpkg", quiet = T)
renta_sf <- left_join(sscc_2021_val, rnt_val21, by="CUSEC")Creamos intervalos:
breaks <- classInt::classIntervals(var = renta_sf$renta, n=6, style = "quantile")Warning in classInt::classIntervals(var = renta_sf$renta, n = 6,
style = "quantile"): var has missing values, omitted in finding
classes
breaks <- breaks$brks
breaks <- round(breaks, -2)
class <- base::findInterval(x=pull(renta_sf, renta), vec = breaks, all.inside = T)
renta_sf <- renta_sf %>% mutate(class = class)
labels <- tibble(class = 1:6,
min = scales::number(breaks[1:6], big.mark = ".", decimal.mark = ","),
max = scales::number(breaks[2:7], big.mark = ".", decimal.mark = ","),
interval = paste0(min, " - ", max))
renta_sf <- inner_join(renta_sf, labels, by = "class")
renta_sf <- mutate(renta_sf, interval = factor(interval, levels = labels$interval))En ocasiones, puede ser útil reducir la calidad de la cartografía (número de puntos que conforman los polígonos). Para ello, podemos usar, por ejemplo, el paquete rmapshaper(Teucher y Russell, 2025):
#install.packages("rmapshaper")
#library(rmapshaper)
renta_sf <- rmapshaper::ms_simplify(renta_sf, keep = 0.25)Creamos la paleta de colores:
#install.packages("RColorBrewer")
#library(RColorBrewer)
#RColorBrewer::display.brewer.all()
palette <- RColorBrewer::brewer.pal(n=6, name = "YlGn")
colors <- tibble(clases = labels$interval, palette)
colors <- mutate(colors, clases = factor(clases, levels = labels$interval))
renta_sf <- mutate(renta_sf, clases = interval)
renta_sf <- inner_join(renta_sf, colors, by = "clases")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() %>%
leaflet::addTiles() %>%
leaflet::addProviderTiles(provider = providers$CartoDB.Positron, group = "CartoDB") %>%
leaflet::addPolygons(data = renta_sf,
color = "transparent",
fillColor = renta_sf$palette,
fillOpacity = 0.5,
smoothFactor = 0.5,
label = ~renta_sf$CUSEC,
popup = paste0("<b>Renta bruta media por hogar</b>: ", renta_sf$renta, " €", "<br>",
"<b>Municipio</b>: ", renta_sf$NMUN)) %>%
leaflet::addLegend("topright",
colors = colors$palette,
labels = colors$clases,
title = "Renta bruta media por hogar") %>%
leaflet.extras::addFullscreenControl() %>%
leaflet.extras::addResetMapButton() %>%
leaflet.extras::addSearchOSM() %>%
leaflet::addMiniMap(tiles = "Esri.WorldStreetMap", position = "bottomleft")Para una mayor customización de las etiquetas (label / popup), se puede usar el paquete htmltools(Cheng, Sievert, et al., 2025).