1. Contexto

El Valle del Cauca es un departamento ubicado en el suroccidente de Colombia, atravesado longitudinalmente por el río Cauca entre las cordilleras Central y Occidental. Esta configuración genera un contraste geomorfológico marcado: un valle aluvial plano (con pendientes generalmente inferiores al 3%), dedicado en buena parte a la agroindustria de la caña de azúcar, flanqueado por vertientes montañosas de las dos cordilleras con pendientes fuertes y alta variabilidad de aspecto. En un mismo departamento coexisten así zonas planas de vega, piedemonte y alta montaña (incluyendo sectores del Parque Nacional Natural Farallones de Cali y del macizo occidental hacia el Pacífico).

En este notebook usaremos el paquete MultiscaleDTM para calcular pendiente y aspecto a partir de un modelo digital de elevación (DEM) del Valle del Cauca, y exactextractr para resumir esos valores por municipio (el departamento cuenta con 42 municipios, con capital en Cali). A diferencia de una versión anterior de este notebook, aquí los datos de entrada (límites municipales y DEM) se descargan automáticamente desde el código, sin necesidad de conseguirlos manualmente.

2. Configuración

Los paquetes requeridos deben instalarse previamente desde la Consola.

## CONFIGURACIÓN
# INSTALAR ESTOS PAQUETES DESDE LA CONSOLA, NO DESDE ESTE CHUNK
paquetes <- c("MultiscaleDTM", "exactextractr", "geodata", "elevatr", "sf", "terra", "leaflet")
install.packages(paquetes)

Limpiamos la memoria:

rm(list = ls())

Cargamos las librerías necesarias:

library(geodata)
library(elevatr)
library(sf)
library(leaflet)
library(terra)
library(MultiscaleDTM)
library(exactextractr)

Al ejecutar cada chunk, presta atención a los mensajes de error o advertencia de conflictos entre funciones de distintos paquetes (por ejemplo terra::extract vs tidyr::extract). Para evitar ambigüedades, varias funciones se llamarán usando la notación larga paquete::funcion.

3. Descarga de datos

3.1 Límites municipales del Valle del Cauca

Usamos el paquete geodata, que descarga directamente los límites administrativos de la base GADM, para obtener los municipios de Colombia y filtrar los que pertenecen al Valle del Cauca. level = 2 corresponde al nivel municipal.

carpeta_datos <- "valle_del_cauca"
dir.create(carpeta_datos, showWarnings = FALSE)

# descarga los municipios de toda Colombia (se guarda en caché local)
mpios_col <- geodata::gadm(country = "COL", level = 2, path = carpeta_datos)

# convertimos a objeto sf y filtramos el departamento
mpios_col_sf <- sf::st_as_sf(mpios_col)
munic <- mpios_col_sf[mpios_col_sf$NAME_1 == "Valle del Cauca", ]

# nos quedamos con las columnas relevantes y renombramos para mayor claridad
munic <- munic[, c("NAME_1", "NAME_2", "geometry")]
names(munic)[1:2] <- c("departamento", "municipio")

# al filtrar/seleccionar columnas, sf a veces deja la geometría como el tipo
# genérico "GEOMETRY" en lugar de "MULTIPOLYGON", lo cual rompe funciones como
# elevatr::get_elev_raster(). Forzamos el tipo correcto:
munic <- sf::st_cast(munic, "MULTIPOLYGON")
munic <- sf::st_make_valid(munic)

(munic)

Guardamos esta capa como geopackage para reutilizarla sin tener que descargarla de nuevo:

sf::st_write(munic, file.path(carpeta_datos, "valle_del_cauca.gpkg"), append = FALSE, quiet = TRUE)

Visualicemos rápidamente los municipios descargados:

plot(sf::st_geometry(munic), col = "darkseagreen3", border = "gray30",
     main = "Municipios del Valle del Cauca")

3.2 Modelo digital de elevación (DEM)

Descargamos el DEM correspondiente al área del Valle del Cauca con elevatr, usando el polígono departamental como máscara de recorte. El parámetro z controla la resolución (entre mayor el zoom, mayor la resolución y el peso del archivo; z = 9 es un buen punto de partida para un departamento completo).

dem_raw <- elevatr::get_elev_raster(locations = munic, z = 9, clip = "locations")
## Mosaicing & Projecting
## Clipping DEM to locations
## Note: Elevation units are in meters.
dem <- terra::rast(dem_raw)

terra::writeRaster(dem, file.path(carpeta_datos, "elev_valle_z9.tif"), overwrite = TRUE)

(dem)
## class       : SpatRaster
## size        : 1425, 1342, 1  (nrow, ncol, nlyr)
## resolution  : 0.001371367, 0.001371367  (x, y)
## extent      : -77.54495, -75.70458, 3.061129, 5.015327  (xmin, xmax, ymin, ymax)
## coord. ref. : +proj=longlat +datum=WGS84 +no_defs
## source(s)   : memory
## name        : file28e05d169
## min value   :          -564
## max value   :          4204
paleta_relieve <- grDevices::colorRampPalette(
  c("#1a4314", "#3d7a1f", "#8fae3d", "#d9c66b", "#a97d4f", "#6b4423", "#4a2e1a")
)(100)

terra::plot(dem, main = "DEM del Valle del Cauca", col = paleta_relieve)

4. Preparación del DEM

dem2 <- terra::aggregate(dem, 2, "mean")
dem3 <- terra::crop(dem2, munic, mask = TRUE)

5. Transformación de coordenadas (10%)

Para calcular atributos de terreno, el DEM debe estar en coordenadas planas. Desde 2020 el IGAC adoptó el sistema de coordenadas planas con origen único nacional (EPSG:9377).

Transforma el DEM y la capa de municipios a este sistema:

# revisa los parámetros de esta función en
# https://rdrr.io/github/rspatial/terra/man/project.html
(dem_plane <- terra::project(dem3, "EPSG:9377"))
## class       : SpatRaster
## size        : 715, 676, 1  (nrow, ncol, nlyr)
## resolution  : 304.2633, 304.2633  (x, y)
## extent      : 4494648, 4700330, 1896389, 2113937  (xmin, xmax, ymin, ymax)
## coord. ref. : MAGNA-SIRGAS 2018 / Origen-Nacional (EPSG:9377)
## source(s)   : memory
## name        : file28e05d169
## min value   :   -319.718994
## max value   :    4113.18457
# revisa los parámetros de esta función en
# https://github.com/rstudio/cheatsheets/blob/main/sf.pdf
(munic_plane <- sf::st_transform(munic, "EPSG:9377"))

6. Cálculo de atributos de terreno (20%)

Calculamos pendiente (slope) y orientación (aspect) con SlpAsp. Consulta ?SlpAsp en la Consola para revisar los parámetros w (tamaño de la ventana de análisis) y method (esquema de vecindad).

# Explica qué es w
# Explica qué es method
# Modifica si es necesario
(slp_asp <- MultiscaleDTM::SlpAsp(
  dem_plane,
  w = c(3, 3),
  unit = "degrees",
  method = "queen",
  metrics = c("slope", "aspect"),
  na.rm = TRUE,
  include_scale = FALSE,
  mask_aspect = TRUE
))
## class       : SpatRaster
## size        : 715, 676, 2  (nrow, ncol, nlyr)
## resolution  : 304.2633, 304.2633  (x, y)
## extent      : 4494648, 4700330, 1896389, 2113937  (xmin, xmax, ymin, ymax)
## coord. ref. : MAGNA-SIRGAS 2018 / Origen-Nacional (EPSG:9377)
## source(s)   : memory
## names       :     slope,    aspect
## min values  :         0,  0.002835
## max values  : 50.832156, 359.99919

Separamos las dos capas resultantes.

Capa 1 (pendiente):

(slope <- subset(slp_asp, 1))
## class       : SpatRaster
## size        : 715, 676, 1  (nrow, ncol, nlyr)
## resolution  : 304.2633, 304.2633  (x, y)
## extent      : 4494648, 4700330, 1896389, 2113937  (xmin, xmax, ymin, ymax)
## coord. ref. : MAGNA-SIRGAS 2018 / Origen-Nacional (EPSG:9377)
## source(s)   : memory
## name        :     slope
## min value   :         0
## max value   : 50.832156
terra::hist(slope,
  main = "Pendiente del Valle del Cauca",
  xlab = "Pendiente (en grados)"
)

Capa 2 (aspecto):

(aspect <- subset(slp_asp, 2))
## class       : SpatRaster
## size        : 715, 676, 1  (nrow, ncol, nlyr)
## resolution  : 304.2633, 304.2633  (x, y)
## extent      : 4494648, 4700330, 1896389, 2113937  (xmin, xmax, ymin, ymax)
## coord. ref. : MAGNA-SIRGAS 2018 / Origen-Nacional (EPSG:9377)
## source(s)   : memory
## name        :    aspect
## min value   :  0.002835
## max value   : 359.99919
terra::hist(aspect,
  main = "Aspecto del Valle del Cauca",
  xlab = "Aspecto (en grados)"
)

Convertimos la pendiente de grados a porcentaje, unidad de uso común en clasificación agrológica en Colombia:

(slope_perc <- tan(slope * (pi / 180)) * 100)
## class       : SpatRaster
## size        : 715, 676, 1  (nrow, ncol, nlyr)
## resolution  : 304.2633, 304.2633  (x, y)
## extent      : 4494648, 4700330, 1896389, 2113937  (xmin, xmax, ymin, ymax)
## coord. ref. : MAGNA-SIRGAS 2018 / Origen-Nacional (EPSG:9377)
## source(s)   : memory
## name        :      slope
## min value   :          0
## max value   : 122.752709
terra::hist(slope_perc,
  main = "Pendiente del Valle del Cauca",
  xlab = "Pendiente (en porcentaje)"
)

Dada la geografía del departamento, se espera una distribución bimodal: un pico marcado cerca de 0% correspondiente al valle aluvial del río Cauca (zona de agroindustria cañera), y una cola larga de valores altos asociada a las vertientes de las cordilleras Central y Occidental.

7. Cálculo de estadísticas zonales (20%)

Usamos la clasificación de pendientes del IGAC para fines agrológicos y calculamos el promedio de pendiente por municipio.

m <- c(
  0, 3, 1,
  3, 7, 2,
  7, 12, 3,
  12, 25, 4,
  25, 50, 5,
  50, 75, 6,
  75, 160, 7
)
m <- matrix(m, ncol = 3, byrow = TRUE)
rc <- terra::classify(slope_perc, m, right = TRUE)

Calculamos estadísticas zonales con exactextractr:

(munic$mean_slope <- exactextractr::exact_extract(slope_perc, munic, "mean"))
## Warning in .local(x, y, ...): Polygons transformed to raster CRS (EPSG: 9377) .
## To avoid on-the-fly coordinate transformation, ensure that st_crs() returns the
## same result for both raster and polygon inputs.
##   |                                                                              |                                                                      |   0%  |                                                                              |==                                                                    |   2%  |                                                                              |===                                                                   |   5%  |                                                                              |=====                                                                 |   7%  |                                                                              |=======                                                               |  10%  |                                                                              |========                                                              |  12%  |                                                                              |==========                                                            |  14%  |                                                                              |============                                                          |  17%  |                                                                              |=============                                                         |  19%  |                                                                              |===============                                                       |  21%  |                                                                              |=================                                                     |  24%  |                                                                              |==================                                                    |  26%  |                                                                              |====================                                                  |  29%  |                                                                              |======================                                                |  31%  |                                                                              |=======================                                               |  33%  |                                                                              |=========================                                             |  36%  |                                                                              |===========================                                           |  38%  |                                                                              |============================                                          |  40%  |                                                                              |==============================                                        |  43%  |                                                                              |================================                                      |  45%  |                                                                              |=================================                                     |  48%  |                                                                              |===================================                                   |  50%  |                                                                              |=====================================                                 |  52%  |                                                                              |======================================                                |  55%  |                                                                              |========================================                              |  57%  |                                                                              |==========================================                            |  60%  |                                                                              |===========================================                           |  62%  |                                                                              |=============================================                         |  64%  |                                                                              |===============================================                       |  67%  |                                                                              |================================================                      |  69%  |                                                                              |==================================================                    |  71%  |                                                                              |====================================================                  |  74%  |                                                                              |=====================================================                 |  76%  |                                                                              |=======================================================               |  79%  |                                                                              |=========================================================             |  81%  |                                                                              |==========================================================            |  83%  |                                                                              |============================================================          |  86%  |                                                                              |==============================================================        |  88%  |                                                                              |===============================================================       |  90%  |                                                                              |=================================================================     |  93%  |                                                                              |===================================================================   |  95%  |                                                                              |====================================================================  |  98%  |                                                                              |======================================================================| 100%
##  [1]  5.5744586  5.1779618 19.1408596 21.1887646 26.1608505 11.5885410
##  [7]  8.1660175 14.9600506 24.0302525  0.3369108  6.6544151 24.1689396
## [13] 30.2476768 26.9027443 17.6654491 24.2423954 23.3342361 22.7411823
## [19]  8.6025810 23.9334431 12.9214354 14.9278593 11.1586790  6.1246181
## [25]  8.7152472 15.3353434 21.0480251 14.9463530 17.8954411 12.4469519
## [31] 12.2564573 15.1116304 18.7486839 16.3780346 22.5365429 21.8216362
## [37]  5.8662834 23.8242264 14.3155890 14.1256762 15.4605160  3.4692101
hist(munic$mean_slope,
  main = "Pendiente media por municipio - Valle del Cauca",
  xlab = "Pendiente (en porcentaje)"
)

(munic$class <- exactextractr::exact_extract(rc, munic, "mode"))
## Warning in .local(x, y, ...): Polygons transformed to raster CRS (EPSG: 9377) .
## To avoid on-the-fly coordinate transformation, ensure that st_crs() returns the
## same result for both raster and polygon inputs.
##   |                                                                              |                                                                      |   0%  |                                                                              |==                                                                    |   2%  |                                                                              |===                                                                   |   5%  |                                                                              |=====                                                                 |   7%  |                                                                              |=======                                                               |  10%  |                                                                              |========                                                              |  12%  |                                                                              |==========                                                            |  14%  |                                                                              |============                                                          |  17%  |                                                                              |=============                                                         |  19%  |                                                                              |===============                                                       |  21%  |                                                                              |=================                                                     |  24%  |                                                                              |==================                                                    |  26%  |                                                                              |====================                                                  |  29%  |                                                                              |======================                                                |  31%  |                                                                              |=======================                                               |  33%  |                                                                              |=========================                                             |  36%  |                                                                              |===========================                                           |  38%  |                                                                              |============================                                          |  40%  |                                                                              |==============================                                        |  43%  |                                                                              |================================                                      |  45%  |                                                                              |=================================                                     |  48%  |                                                                              |===================================                                   |  50%  |                                                                              |=====================================                                 |  52%  |                                                                              |======================================                                |  55%  |                                                                              |========================================                              |  57%  |                                                                              |==========================================                            |  60%  |                                                                              |===========================================                           |  62%  |                                                                              |=============================================                         |  64%  |                                                                              |===============================================                       |  67%  |                                                                              |================================================                      |  69%  |                                                                              |==================================================                    |  71%  |                                                                              |====================================================                  |  74%  |                                                                              |=====================================================                 |  76%  |                                                                              |=======================================================               |  79%  |                                                                              |=========================================================             |  81%  |                                                                              |==========================================================            |  83%  |                                                                              |============================================================          |  86%  |                                                                              |==============================================================        |  88%  |                                                                              |===============================================================       |  90%  |                                                                              |=================================================================     |  93%  |                                                                              |===================================================================   |  95%  |                                                                              |====================================================================  |  98%  |                                                                              |======================================================================| 100%
##  [1] 1 1 5 4 5 1 1 4 5 1 1 5 5 5 1 5 5 5 1 5 1 4 1 1 1 1 5 4 4 4 1 1 4 5 4 5 2 4
## [39] 4 4 4 1
hist(munic$class,
  main = "Pendiente reclasificada - Valle del Cauca",
  xlab = "Pendiente (como categoría)"
)

Tabla resumen ordenada por pendiente media, de menor a mayor:

tabla_resumen <- sf::st_drop_geometry(munic[, c("municipio", "mean_slope", "class")])
tabla_resumen <- tabla_resumen[order(tabla_resumen$mean_slope), ]
(tabla_resumen)

Se espera que municipios ubicados sobre la vega del río Cauca (por ejemplo, en el eje Cali-Palmira-Buga-Tuluá-Cartago) muestren clases bajas (1-2), mientras que municipios de cordillera (hacia el Pacífico u hacia el oriente del departamento) presenten clases más altas.

Regresamos las capas a coordenadas geográficas para su visualización en leaflet:

# Pendiente reclasificada
(rc.geo <- terra::project(rc, "EPSG:4326"))
## class       : SpatRaster
## size        : 719, 678, 1  (nrow, ncol, nlyr)
## resolution  : 0.002742672, 0.002742672  (x, y)
## extent      : -77.55587, -75.69634, 3.053583, 5.025564  (xmin, xmax, ymin, ymax)
## coord. ref. : lon/lat WGS 84 (EPSG:4326)
## source(s)   : memory
## name        : slope
## min value   :     0
## max value   :     7
# Pendiente en porcentaje
(slope.geo <- terra::project(slope_perc, "EPSG:4326"))
## class       : SpatRaster
## size        : 719, 678, 1  (nrow, ncol, nlyr)
## resolution  : 0.002742672, 0.002742672  (x, y)
## extent      : -77.55587, -75.69634, 3.053583, 5.025564  (xmin, xmax, ymin, ymax)
## coord. ref. : lon/lat WGS 84 (EPSG:4326)
## source(s)   : memory
## name        :      slope
## min value   :          0
## max value   : 114.035812

8. Visualización de la pendiente del terreno (20%)

Definimos una paleta de colores para la pendiente:

# amplía el número de colores para mejorar la visualización
# https://r-graph-gallery.com/42-colors-names.html
palredgreen <- leaflet::colorNumeric(
  c("darkseagreen3", "yellow2", "orange", "brown2", "darkred"),
  terra::values(slope.geo),
  na.color = "transparent"
)

Generamos el mapa interactivo, centrado sobre el Valle del Cauca:

leaflet::leaflet(munic) %>%
  leaflet::addTiles() %>%
  leaflet::setView(-76.3, 3.9, 8) %>%
  leaflet::addPolygons(
    color = "gray", weight = 1.0, smoothFactor = 0.5,
    opacity = 0.4, fillOpacity = 0.10,
    popup = paste(
      "Municipio: ", munic$municipio, "<br>",
      "Clase de pendiente: ", munic$class, "<br>"
    )
  ) %>%
  leaflet::addRasterImage(slope.geo, colors = palredgreen, opacity = 0.8) %>%
  leaflet::addLegend(
    pal = palredgreen, values = terra::values(slope.geo),
    title = "Pendiente del terreno en el Valle del Cauca (%)"
  )

Haz clic en distintos puntos del mapa para consultar el nombre del municipio y su clase de pendiente predominante.

9. Visualización alternativa de la pendiente (15%)

Visualiza la pendiente del terreno en grados y añade etiquetas con el nombre de cada municipio junto a su pendiente media. Conserva los popups tal como en el mapa anterior.

Calculamos la pendiente media por municipio en grados (además de la que ya teníamos en porcentaje), y llevamos la capa de pendiente en grados a coordenadas geográficas para poder mapearla:

munic$mean_slope_deg <- exactextractr::exact_extract(slope, munic, "mean")
## Warning in .local(x, y, ...): Polygons transformed to raster CRS (EPSG: 9377) .
## To avoid on-the-fly coordinate transformation, ensure that st_crs() returns the
## same result for both raster and polygon inputs.
##   |                                                                              |                                                                      |   0%  |                                                                              |==                                                                    |   2%  |                                                                              |===                                                                   |   5%  |                                                                              |=====                                                                 |   7%  |                                                                              |=======                                                               |  10%  |                                                                              |========                                                              |  12%  |                                                                              |==========                                                            |  14%  |                                                                              |============                                                          |  17%  |                                                                              |=============                                                         |  19%  |                                                                              |===============                                                       |  21%  |                                                                              |=================                                                     |  24%  |                                                                              |==================                                                    |  26%  |                                                                              |====================                                                  |  29%  |                                                                              |======================                                                |  31%  |                                                                              |=======================                                               |  33%  |                                                                              |=========================                                             |  36%  |                                                                              |===========================                                           |  38%  |                                                                              |============================                                          |  40%  |                                                                              |==============================                                        |  43%  |                                                                              |================================                                      |  45%  |                                                                              |=================================                                     |  48%  |                                                                              |===================================                                   |  50%  |                                                                              |=====================================                                 |  52%  |                                                                              |======================================                                |  55%  |                                                                              |========================================                              |  57%  |                                                                              |==========================================                            |  60%  |                                                                              |===========================================                           |  62%  |                                                                              |=============================================                         |  64%  |                                                                              |===============================================                       |  67%  |                                                                              |================================================                      |  69%  |                                                                              |==================================================                    |  71%  |                                                                              |====================================================                  |  74%  |                                                                              |=====================================================                 |  76%  |                                                                              |=======================================================               |  79%  |                                                                              |=========================================================             |  81%  |                                                                              |==========================================================            |  83%  |                                                                              |============================================================          |  86%  |                                                                              |==============================================================        |  88%  |                                                                              |===============================================================       |  90%  |                                                                              |=================================================================     |  93%  |                                                                              |===================================================================   |  95%  |                                                                              |====================================================================  |  98%  |                                                                              |======================================================================| 100%
(slope_deg.geo <- terra::project(slope, "EPSG:4326"))
## class       : SpatRaster
## size        : 719, 678, 1  (nrow, ncol, nlyr)
## resolution  : 0.002742672, 0.002742672  (x, y)
## extent      : -77.55587, -75.69634, 3.053583, 5.025564  (xmin, xmax, ymin, ymax)
## coord. ref. : lon/lat WGS 84 (EPSG:4326)
## source(s)   : memory
## name        :     slope
## min value   :         0
## max value   : 48.722939

Definimos una paleta para la pendiente en grados y calculamos los centroides de cada municipio, donde ubicaremos las etiquetas con el nombre y la pendiente media:

pal_deg <- leaflet::colorNumeric(
  c("darkseagreen3", "yellow2", "orange", "brown2", "darkred"),
  terra::values(slope_deg.geo),
  na.color = "transparent"
)
centros <- sf::st_centroid(munic)
## Warning: st_centroid assumes attributes are constant over geometries
etiquetas_slope <- paste0(munic$municipio, ": ", round(munic$mean_slope_deg, 1), "°")

Generamos el mapa: pendiente en grados como capa raster, los mismos popups del mapa anterior, y etiquetas permanentes con el nombre del municipio y su pendiente media:

leaflet::leaflet(munic) %>%
  leaflet::addTiles() %>%
  leaflet::setView(-76.3, 3.9, 8) %>%
  leaflet::addPolygons(
    color = "gray", weight = 1.0, smoothFactor = 0.5,
    opacity = 0.4, fillOpacity = 0.10,
    popup = paste(
      "Municipio: ", munic$municipio, "<br>",
      "Clase de pendiente: ", munic$class, "<br>"
    )
  ) %>%
  leaflet::addRasterImage(slope_deg.geo, colors = pal_deg, opacity = 0.8) %>%
  leaflet::addLegend(
    pal = pal_deg, values = terra::values(slope_deg.geo),
    title = "Pendiente del terreno en el Valle del Cauca (grados)"
  ) %>%
  leaflet::addLabelOnlyMarkers(
    data = centros,
    label = etiquetas_slope,
    labelOptions = leaflet::labelOptions(
      noHide = TRUE, direction = "center", textOnly = TRUE,
      style = list("font-weight" = "bold", "font-size" = "10px", "color" = "black")
    )
  )

Describe el resultado obtenido y su significado, relacionándolo con la posición del municipio dentro del valle o sobre alguna de las dos cordilleras.

10. Visualización del aspecto del terreno (15%)

Visualiza el aspecto del terreno usando una paleta cíclica adecuada para una variable circular (0°-360°).

Como el aspecto es una variable circular (0° y 360° representan la misma orientación, Norte), no tiene sentido usar una paleta continua tipo semáforo como la de la pendiente. Primero llevamos la capa de aspecto (en grados) a coordenadas geográficas:

(aspect.geo <- terra::project(aspect, "EPSG:4326"))
## class       : SpatRaster
## size        : 719, 678, 1  (nrow, ncol, nlyr)
## resolution  : 0.002742672, 0.002742672  (x, y)
## extent      : -77.55587, -75.69634, 3.053583, 5.025564  (xmin, xmax, ymin, ymax)
## coord. ref. : lon/lat WGS 84 (EPSG:4326)
## source(s)   : memory
## name        :     aspect
## min value   :   0.130264
## max value   : 359.940155

Luego la reclasificamos en 8 direcciones cardinales/intercardinales y usamos una paleta tipo “rosa de los vientos”, donde direcciones opuestas tienen colores contrastantes:

m2 <- matrix(c(
      0,  22.5, 1,   # N
   22.5,  67.5, 2,   # NE
   67.5, 112.5, 3,   # E
  112.5, 157.5, 4,   # SE
  157.5, 202.5, 5,   # S
  202.5, 247.5, 6,   # SW
  247.5, 292.5, 7,   # W
  292.5, 337.5, 8,   # NW
  337.5,   360, 1    # N (cierra el círculo)
), ncol = 3, byrow = TRUE)

aspect_class <- terra::classify(aspect.geo, m2, right = FALSE)
direcciones <- c("N", "NE", "E", "SE", "S", "SW", "W", "NW")
colores_direccion <- c(
  "red", "darkorange", "gold", "chartreuse3",
  "forestgreen", "cyan3", "blue", "purple"
)

pal_aspect <- leaflet::colorFactor(
  palette = colores_direccion,
  domain = 1:8,
  na.color = "transparent"
)

Generamos el mapa de orientación del terreno:

leaflet::leaflet(munic) %>%
  leaflet::addTiles() %>%
  leaflet::setView(-76.3, 3.9, 8) %>%
  leaflet::addPolygons(
    color = "gray", weight = 1.0, smoothFactor = 0.5,
    opacity = 0.4, fillOpacity = 0.10,
    popup = paste("Municipio: ", munic$municipio, "<br>")
  ) %>%
  leaflet::addRasterImage(aspect_class, colors = pal_aspect, opacity = 0.8) %>%
  leaflet::addLegend(
    colors = colores_direccion, labels = direcciones,
    title = "Orientación del terreno (aspecto)", opacity = 1
  )
## Warning in colors(.): Some values were outside the color scale and will be
## treated as NA

Bibliografía

Si reutilizas el código de este notebook, por favor cita este trabajo.

Fuentes de datos:

Reproducibilidad

sessionInfo()
## R version 4.6.0 (2026-04-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 10 x64 (build 19045)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=Spanish_Spain.utf8  LC_CTYPE=Spanish_Spain.utf8   
## [3] LC_MONETARY=Spanish_Spain.utf8 LC_NUMERIC=C                  
## [5] LC_TIME=Spanish_Spain.utf8    
## 
## time zone: America/Bogota
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] exactextractr_0.10.1 MultiscaleDTM_1.0.1  leaflet_2.2.3       
## [4] sf_1.1-1             elevatr_0.99.1       geodata_0.6-9       
## [7] terra_1.9-27        
## 
## loaded via a namespace (and not attached):
##  [1] xfun_0.57          bslib_0.10.0       raster_3.6-32      htmlwidgets_1.6.4 
##  [5] lattice_0.22-9     vctrs_0.7.3        tools_4.6.0        crosstalk_1.2.2   
##  [9] generics_0.1.4     curl_7.1.0         rgl_1.3.36         tibble_3.3.1      
## [13] proxy_0.4-29       pkgconfig_2.0.3    KernSmooth_2.23-26 RColorBrewer_1.1-3
## [17] lifecycle_1.0.5    farver_2.1.2       compiler_4.6.0     progress_1.2.3    
## [21] codetools_0.2-20   httpuv_1.6.17      htmltools_0.5.9    class_7.3-23      
## [25] sass_0.4.10        yaml_2.3.12        later_1.4.8        pillar_1.11.1     
## [29] crayon_1.5.3       jquerylib_0.1.4    classInt_0.4-11    slippymath_0.3.1  
## [33] cachem_1.1.0       wk_0.9.5           mime_0.13          tidyselect_1.2.1  
## [37] digest_0.6.39      dplyr_1.2.1        purrr_1.2.2        fastmap_1.2.0     
## [41] grid_4.6.0         cli_3.6.6          magrittr_2.0.5     base64enc_0.1-6   
## [45] e1071_1.7-17       scales_1.4.0       prettyunits_1.2.0  promises_1.5.0    
## [49] rappdirs_0.3.4     sp_2.2-1           rmarkdown_2.31     httr_1.4.8        
## [53] otel_0.2.0         progressr_0.19.0   png_0.1-9          hms_1.1.4         
## [57] shiny_1.13.0       evaluate_1.0.5     knitr_1.51         s2_1.1.9          
## [61] rlang_1.2.0        Rcpp_1.1.1-1.1     xtable_1.8-8       glue_1.8.1        
## [65] DBI_1.3.0          rstudioapi_0.18.0  jsonlite_2.0.0     R6_2.6.1          
## [69] units_1.0-1