Code chunks for the #30DayMapChallenge 2025

Author
Published

November 1, 2025

Here are the pieces of code used to produce the maps.

Day 1

# Load required packages
library(rgbif)
library(ggplot2)
library(sf)
library(dplyr)
library(rnaturalearth)
library(magick)

# Get world map
worldmap <- ne_countries(scale = 'medium', type = 'map_units',
    returnclass = 'sf')

# Get occurrence data for Cyperus papyrus
cypepap <- occ_search(scientificName = "Cyperus papyrus", hasCoordinate = TRUE)

cypepap_map <- st_as_sf(cypepap$data, 
  coords = c("decimalLongitude", "decimalLatitude"),
  crs = "+proj=longlat +datum=WGS84") %>%
  mutate(origin = continent == "AFRICA")

# Create a global bounding box (in lon/lat)
N <- 200
bg_coords <- cbind(c(-180, rep(180, N), rep(-180, N)),
  c(90, seq(90, -90, length.out = N), seq(-90, 90, length.out = N)))

bg <- st_as_sf(
  st_sfc(st_polygon(list(
    bg_coords
  ))),
  crs = 4326
)

# Create the map
water <- "#ccfcff"
m <- ggplot() + 
  geom_sf(data = bg, fill = water, color = NA) +
  geom_sf(data = worldmap, fill = "#b4b4b4", color = water) +
  geom_sf(data = cypepap_map, col = "red", show.legend = FALSE,
    cex = 1.2) +
  geom_sf(data = cypepap_map, aes(color = origin), show.legend = FALSE,
    cex = 0.5) +
  coord_sf(crs = st_crs("ESRI:54030")) +
  scale_color_manual(
    values = c("TRUE" = "#f5b53e", "FALSE" = "#fdfd8c")
  ) +
  theme_minimal() +
  theme(
    axis.text = element_blank(),
    panel.grid.major = element_blank(),
    plot.background = element_rect(fill = "transparent", color = NA)
  )

# Save the map as PNG file
img_name <- "01-points.png"
ggsave(img_name, plot = m, width = 8, height = 6, dpi = 300,
  bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 2

# Load required packages
library(rnaturalearth)
library(sf)
library(ggplot2)
library(dplyr)
library(units)
library(magick)

# download the South America map and crop continental Chile
south_america <- ne_countries(continent = "South America", scale = "large",
  returnclass = "sf") %>%
  st_make_valid() %>%
  st_crop(y = st_bbox(c(xmin = -77, ymin = -57, xmax = -65, ymax = -17)))

# Extract Chile and convert to UTM
chile <- south_america %>%
  st_transform(crs = "EPSG:32718") %>%
  filter(admin == "Chile")

# Extract nodes
nodes <- chile %>%
  st_cast("MULTILINESTRING") %>%
  st_cast("POINT")

# Compute the pairwise distance matrix
dist_matrix <- st_distance(nodes)

# Find the indices of the two farthest points
max_idx <- which(dist_matrix == max(dist_matrix), arr.ind = TRUE)[1, ]

max_line <- st_sfc(
  st_linestring(
    rbind(
      st_coordinates(nodes[max_idx[1], ]),
      st_coordinates(nodes[max_idx[2], ]))
    ), crs = st_crs(nodes))

# Measure length of the line
line_length <- st_length(max_line) %>%
  set_units("km")

# Calculate centroid of line
line_centroid <- st_centroid(max_line)
line_centroid <- st_sf(line_centroid)
line_centroid$distance <- paste0(round(as.numeric(line_length), 1), " km")

# Draw the map
m <- ggplot() + 
  geom_sf(data = chile, fill = "gray", color = NA) +
  geom_sf(data = max_line, color = "red") +
  geom_sf_label(data = line_centroid, aes(label = distance), color = "red",
    angle = 90) +
  theme(
    axis.title = element_blank(),
    axis.text.x = element_text(angle = 90)
  )

# Save the map as PNG file
img_name <- "02-lines.png"
ggsave(img_name, plot = m, dpi = 300, bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 3

# Load required packages
library(rnaturalearth)
library(sf)
library(ggplot2)
library(dplyr)
library(magick)

# download the South America map and crop continental Chile
sam <- ne_countries(continent = "South America", scale = "large",
  returnclass = "sf") %>%
  st_make_valid() %>%
  st_crop(y = st_bbox(c(xmin = -77, ymin = -57, xmax = -65, ymax = -41)))

# Merge neighbouring polygons
touch_matrix <- st_relate(sam, pattern = "F***T****")

# Build groups of connected features
library(igraph)
g <- graph_from_adjacency_matrix(touch_matrix)
groups <- components(g)$membership

# Merge polygons within each group
merged <- sam %>%
  mutate(group = groups) %>%
  group_by(group) %>%
  summarise(geometry = st_union(geometry))

# Split into single polygons and transform
merged <- merged %>%
  st_transform(crs = "EPSG:32718") %>%
  st_cast(to = "POLYGON")

merged <- merged %>%
  mutate(area = st_area(merged))

# Filter islands and classify them by their area
islands <- merged %>%
  filter(area < max(area)) %>%
  mutate(quartile = as.factor(5 - ntile(area, 4)))

# Crop a piece of continent around the islands
bbox <- st_bbox(islands)
bbox["ymax"] <- bbox["ymax"] + 100000
bbox["xmax"] <- bbox["xmax"] + 100000
bbox["xmin"] <- bbox["xmin"] - 100000

continent <- merged %>%
  filter(area == max(area)) %>%
  st_crop(y = bbox)

# Draw the graphic
m <- ggplot() +
  geom_sf(data = continent, fill = "#78d7e4ff", color = NA, alpha = 0.5) +
  geom_sf(data = islands, aes(fill = quartile), color = NA) +
  scale_fill_manual(
    values = c(
      "1" = "#ffca57ff",
      "2" = "#d0352e",
      "3" = "#4ab85cff",
      "4" = "#2427b1ff"
    ),
    name = "Area\nQuartile"  # legend title
  ) +
  xlim(450000, 1200000.) +
  ylim(3775178.7, 5340000) +
  theme(
    axis.text.x = element_text(angle = 90),
    legend.title = element_text(size = 9),
    panel.background = element_rect(fill = "#d7eef1ff")
  )
m

# Save the map as PNG file
img_name <- "03-polygons.png"
ggsave(img_name, plot = m, height = 9, width = 6, dpi = 300, bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 4

# Load packages
library(dplyr)
library(sf)
library(terra)
library(leaflet)
library(htmlwidgets)
library(webshot2)

# Import Data
track_points <- read.csv("track_points.csv") %>%
  st_as_sf(coords = c("X", "Y"), crs = 4326) %>%
  st_transform(crs = "EPSG:21037")

# Get bounding box (expand M and round to 100 m)
My <- 100000 # Margin added to top and bottom
Mx <- 400000 # Margin added to left and right
Z <- 100 # Zell resolution in meters
bbox <- (round(st_bbox(track_points)/Z) * Z) + c(-Mx, -My, Mx, My)

# Get raster of 100 m resolution
r <- rast(
  nrows = (bbox["ymax"] - bbox["ymin"])/Z,
  ncols = (bbox["xmax"] - bbox["xmin"])/Z,
  xmin = bbox["xmin"], xmax = bbox["xmax"],
  ymin = bbox["ymin"], ymax = bbox["ymax"],
  crs = "EPSG:21037"
)
values(r) <- 0

# pierced rasters
v_buf <- buffer(vect(track_points), width = 1000)
r_masked <- mask(r, v_buf, inverse = TRUE)

# visualize with leaflet
m <- leaflet() %>%
  addTiles() %>%
  addRasterImage(r_masked, color = "black", opacity = 0.5) %>%
  setView(lng = 36.45, lat = -0.16, zoom = 8) %>%
  addScaleBar()

# Save map
saveWidget(m, "04-my-data.html", selfcontained = TRUE)
webshot2::webshot("04-my-data.html", file = "04-my-data.png")

Day 5

# Load packages
library(elevatr)
library(sf)
library(terra)
library(ggplot2)
library(tidyterra)
library(magick)

# create an sf polygon for the bbox (CRS WGS84)
bbox <- st_as_sfc(st_bbox(c(
    xmin = 7.18,
    ymin = 50.64,
    xmax = 7.3,
    ymax = 50.70
  ), crs = st_crs(4326)))
bbox <- st_sf(geometry = bbox)

# z = zoom level / resolution control (higher = finer). Typical values 9-14.
dem_raster <- get_elev_raster(locations = bbox, z = 14, clip = "locations")

# convert to terra SpatRaster
dem_terra <- rast(dem_raster)
dem_terra

# project to UTM
dem_utm <- project(dem_terra, "EPSG:25832")

# radius from m to zells
r <- 100 # radius in m
r <- round(r/mean(res(dem_utm))) # radius converted to raster cells

# Prepare windows

# circle (From https://github.com/kamapu/spatialist)
d <- 2*r + 1 # diameter
r_ind <- matrix(rep(1:d, times = d), nrow = d) # row index
c_ind <- matrix(rep(1:d, times = d), nrow = d, byrow = TRUE) # col index

Dist <- sqrt(((r + 1) - r_ind)^2 + ((r + 1) - c_ind)^2) # Distance to Center in Zells
Circle <- Dist <= r # Circle

North <- matrix(c(rep(TRUE, times = r*d), rep(FALSE, times = d + r*d)),
    ncol = d, byrow = TRUE) & Circle
South <- matrix(c(rep(FALSE, times = d + r*d), rep(TRUE, times = r*d)),
    ncol = d, byrow = TRUE) & Circle

North[North == TRUE] <- 1
North[North == 0] <- NA
South[South == TRUE] <- 1
South[South == 0] <- NA

# Extract Elevations towards North and South using Focal
# This takes some time

North_r <- focal(dem_utm, North, fun = mean)
South_r <- focal(dem_utm, South, fun = mean)

# Northness
Northness <- South_r - North_r

# Hillshades
slope <- terrain(dem_utm, "slope", unit = "radians")
aspect <- terrain(dem_utm, "aspect", unit = "radians")
hill <- shade(slope, aspect, 40, 270)

# Stack and secure
sieben_gebirge <- rast(list(elevation = dem_utm, northness = Northness, shades = hill))
plot(sieben_gebirge)

writeRaster(sieben_gebirge, "sieben_gebirge.tif")

# Produce map with ggplot
m <- ggplot() +
  geom_spatraster(data = sieben_gebirge, aes(fill = northness)) +
  scale_fill_viridis_c(name = "Northness") +
  geom_spatraster_contour(data = sieben_gebirge, aes(z = elevation),
      breaks = c(125, 150, 175, 225, 250, 275, 325, 350, 375)) +
  geom_spatraster_contour_text(data = sieben_gebirge, aes(z = elevation),
      breaks = c(100, 200, 300, 400), color = "black") +
  theme_minimal()

# Write the image
img_name <- "05-earth.png"
ggsave(img_name, plot = m, height = 6, width = 9, dpi = 300, bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 6

# Load packages
library(dplyr)
library(sf)
library(rnaturalearth)
library(ggplot2)
library(RColorBrewer)
library(magick)

# Download South American map (without Falkland Islands and French Guyana)
sam <- ne_countries(continent = "South America", scale = "small", type = "map_units",
    returnclass = "sf") %>%
  filter(!adm0_iso %in% c("B12", "GUF"))

# Collect population data (https://www.worldometers.info/world-population/south-america-population/)
pop <- c(
  ARG = 45696159,
  CHL = 19764771,
  URY = 3386588,
  BRA = 211998573,
  BOL = 12413315,
  PER = 34217848,
  COL = 52886363,
  VEN = 28405543,
  GUY = 831087,
  SUR = 634431,
  ECU = 18135478,
  PRY = 6929153
)
pop <- data.frame(iso = names(pop), population = pop)

# Calculate centroids and side aspect
bboxes <- as.data.frame(do.call(rbind, lapply(st_geometry(sam), st_bbox))) %>%
  mutate(centr_x = (xmax + xmin)/2, centr_y = (ymax + ymin)/2) %>%
  mutate(aspect = (xmax - xmin)/(ymax - ymin)) %>%
  mutate(name = sam$admin, iso = sam$adm0_iso) %>%
  inner_join(y = pop)

# Calculating new bboxes
c <- 0.0021 # Scaling factor
bboxes_sf <- bboxes %>%
  mutate(height = sqrt(population/aspect) * c) %>%
  mutate(width = aspect * height) %>%
  mutate(
    xmin = centr_x - width/2,
    xmax = centr_x + width/2,
    ymin = centr_y - height/2,
    ymax = centr_y + height/2 
  ) %>%
  select(name, iso, population, xmin, xmax, ymin, ymax) %>%
  rowwise() %>%
  mutate(geometry = st_as_sfc(st_bbox(c(
    xmin = xmin, ymin = ymin, xmax = xmax, ymax = ymax
  ), crs = "EPSG:4326"))) %>%
  st_as_sf()

# Mapping bounding boxes
bboxes_sf$color <- brewer.pal(12, "Set3")

m <- ggplot() +
  geom_sf(data = sam, fill = NA, color = "wheat3", linewidth = 0.8) +
  geom_sf(data = bboxes_sf, aes(fill = color), color = NA, alpha = 0.7) +
  geom_sf_text(data = bboxes_sf, aes(label = iso), color = "grey20", cex = 5) +
  scale_fill_identity() +
  theme_minimal() +
  theme(
    axis.title = element_blank()
  )

# Write the image
img_name <- "06-dimensions.png"
ggsave(img_name, plot = m, height = 10, width = 8, dpi = 300, bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 8

# Load packages
library(osmdata)
library(sf)
library(lwgeom)
library(dplyr)
library(ggplot2)
library(magick)

# Do query to osm
bbox <- st_bbox(c(xmin = 7.012, ymin = 50.761, xmax = 7.13, ymax = 50.861), crs = 4326)

roads <- opq(bbox = bbox) %>%
  add_osm_feature(key = "highway") %>%
  osmdata_sf()

roads_sel <- roads$osm_lines %>%
  select(osm_id, name, highway, lanes, surface)

# Get bounding box and apply coordinates transformation
bbox <- st_as_sfc(bbox) %>%
  st_as_sf() %>%
  st_transform(crs = "EPSG:25832")

roads_sel <- roads_sel %>%
  st_transform(crs = st_crs(bbox)) %>%
  st_crop(y = bbox)

# Split the background
blocks <- st_split(bbox, roads_sel) %>%
  st_collection_extract(type = "POLYGON")

# Produce sizes and size categories
blocks$size <- as.numeric(st_area(blocks))/1000000 # Size in square kilometers
blocks$size_class <- cut(blocks$size,
  breaks = c(0, 0.01, 0.025, 0.1, max(blocks$size)), labels = FALSE)

# Some labels for References
cities <- rbind(
  Niederkassel = c(50.815267, 7.037569),
  Ranzel = c(50.832243, 7.030632),
  Rheidt = c(50.789460, 7.049923),
  Mondorf = c(50.779176, 7.066840),
  Bergheim = c(50.776256, 7.095724),
  Eschmar = c(50.792068, 7.109497),
  Kriegsdorf = c(50.808077, 7.102283),
  Uckendorf = c(50.820749, 7.064948),
  Libur = c(50.838601, 7.070700),
  Hersel = c(50.769734, 7.045189),
  Uedorf = c(50.783207, 7.032862),
  Widdig = c(50.794806, 7.024796),
  Urfeld = c(50.804954, 7.023736)
)
cities <- data.frame(name = rownames(cities), lon = cities[,2], lat = cities[,1])

# Cites to space
cities <- cities %>%
  st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
  st_transform(crs = st_crs(blocks))

# Produce map
m <- ggplot() +
  geom_sf(data = blocks, aes(fill = as.factor(size_class)), color = NA) +
  scale_fill_manual(
    values = c(
      "4" = "#44bd4aff",
      "3" = "#c9ee8cff",
      "2" = "#e68e0bff",
      "1" = "#c53408ff"
    ),
    name = "Size\nCategories"  # legend title
  ) +
  geom_sf_label(data = cities, aes(label = name), col = "black", alpha = 0.8) +
  theme_minimal() +
  theme(
    axis.title = element_blank()
  )

# Write the image
img_name <- "08-urban.png"
ggsave(img_name, plot = m, height = 10, width = 8, dpi = 300, bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 11

# Load packages
library(sf)
library(rnaturalearth)
library(dplyr)
library(rmapshaper)
library(ggplot2)
library(magick)

# Download first-level administrative boundaries (states) for Germany
germany <- ne_states(country = "Germany", returnclass = "sf") %>%
  select(adm1_code, name, woe_label, geometry)

# Calculate Size in square kilometers
germany$size <- as.numeric(st_area(germany))/1000000
germany$main <- "all nodes"

# Simplify polygons, keep 10% of points
germany_s10 <- ms_simplify(
  germany,
  keep = 0.1,
  keep_shapes = TRUE
)
germany_s10$main <- "10% of nodes"

# Simplify polygons, keep 1% of points
germany_s01 <- ms_simplify(
  germany,
  keep = 0.01,
  keep_shapes = TRUE
)
germany_s01$main <- "1% of nodes"

# Simplify polygons, keep 0.5% of points
germany_s005 <- ms_simplify(
  germany,
  keep = 0.005,
  keep_shapes = TRUE
)
germany_s005$main <- "0.5% of nodes"

# Combine all version in one object
germany_all <- bind_rows(germany, germany_s10, germany_s01, germany_s005)
germany_all$main <- factor(germany_all$main,
  levels = c("all nodes", "10% of nodes", "1% of nodes", "0.5% of nodes"))

# Do ggplot
m <- ggplot(germany_all) +
  geom_sf(aes(fill = size), color = "black") +
  facet_wrap(~ main, nrow = 1) +  # all plots in a row
  scale_fill_viridis_c(name = "State\nArea" ~ (km^2), direction = -1) + # single legend
  theme_minimal() +
  theme(
    axis.text = element_blank(),
    axis.ticks = element_blank()
  )

# Write the image
img_name <- "11-minimal-map.png"
ggsave(img_name, plot = m, height = 8, width = 15, dpi = 300, bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 14

# Load packages
library(osmdata)
library(sf)
library(dplyr)
library(ggplot2)
library(RColorBrewer)
library(magick)

# Do query to osm
bbox <- st_bbox(c(
    xmin = 7.081,
    ymin = 50.771,
    xmax = 7.109,
    ymax = 50.785),
  crs = 4326)

roads <- opq(bbox = bbox) %>%
  add_osm_feature(key = "highway") %>%
  osmdata_sf()

# Extract lines and reclassify
roads_mod <- roads$osm_lines %>%
  select(osm_id, name, highway, lanes, surface) %>%
  mutate(highway = recode(highway,
    "living_street" = "residential",
    "service" = "residential",
    "footway" = "path",
    "track" = "path",
    "unclassified" = "path",
    "tertiary" = "secondary"
  )) %>%
  filter(!highway %in% c("platform", "proposed", "steps"))

# Visualize
ggplot() +
  geom_sf(data = roads_mod, aes(color = highway)) +
  facet_wrap(~highway)

# Get bounding box and apply coordinates transformation
bbox <- st_as_sfc(bbox) %>%
  st_as_sf() %>%
  st_transform(crs = "EPSG:25832")

roads_mod <- roads_mod %>%
  st_transform(crs = st_crs(bbox)) %>%
  st_crop(y = bbox)

# Create buffers
buffers <- roads_mod %>%
  mutate(buffer_width = case_when(
    highway == "path" ~ 4,
    highway == "residential" ~ 6,
    highway == "secondary" ~ 10
  )) %>%
  mutate(geometry = st_buffer(geometry, dist = buffer_width)) %>%
  st_union()

# Split background
bg <- st_difference(bbox, buffers) %>%
  st_cast("POLYGON")

# Select colors
palette <- brewer.pal(8, "Accent")

set.seed(42)
bg <- bg %>%
  mutate(rcols = sample(palette, nrow(bg), replace = TRUE))

m <- ggplot(data = bg) +
  geom_sf(aes(fill = rcols), color = NA, alpha = 0.7) +
  geom_sf(fill = NA, color = "#927a4fff", linewidth = 0.8) +
  theme_void() +
  theme(legend.position = "none")
m

# Write the image
img_name <- "14-openstreetmap.png"
ggsave(img_name, plot = m, height = 7, width = 9, dpi = 300, bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 16

# Load packages
library(elevatr)
library(sf)
library(terra)
library(isocubes)
library(grid)
library(magick)

# create an sf polygon for the bbox (CRS WGS84)
bbox <- st_as_sfc(st_bbox(c(
    xmin = 7.18,
    ymin = 50.64,
    xmax = 7.3,
    ymax = 50.70
  ), crs = st_crs(4326)))
bbox <- st_sf(geometry = bbox)

# z = zoom level / resolution control (higher = finer). Typical values 9-14.
dem_raster <- get_elev_raster(locations = bbox, z = 14, clip = "locations")

# convert to terra SpatRaster
dem_terra <- rast(dem_raster)
dem_terra

# project to UTM
dem_utm <- project(dem_terra, "EPSG:25832")

# resample to a grid
N <- 150   # Horizontal cell size in m

template <- rast(ext(dem_utm), 
                 resolution = N, 
                 crs = crs(dem_utm))

# Resample DEM to 100 m grid (bilinear or nearest)
dem100 <- resample(dem_utm, template, method = "bilinear")

# Reclssify into 100 m elevation classes
N <- 50   # Vertical cell size in m
max_elev <- ceiling(max(values(dem100), na.rm = TRUE) / N) * N

class_matrix <- cbind(
  from = seq(0, max_elev - N, N),
  to   = seq(N, max_elev, N),
  becomes = seq(0, max_elev, N) / N
)

dem_class <- classify(dem100, class_matrix)

# Convert raster to data.frame
df <- as.data.frame(dem_class, xy = TRUE)
colnames(df) <- c("xcoord", "ycoord", "class")

# Remove NA
df <- df[!is.na(df$class), ]

# Convert geographic coordinates to grid indices
# (isocubes uses integer grid positions)
df$col <- terra::colFromX(dem_class, df$xcoord)
df$row <- terra::rowFromY(dem_class, df$ycoord)

# Build cube coordinates
coords <- do.call(rbind, lapply(1:nrow(df), function(i) {
  if (df$class[i] == 0) return(NULL)
  z_vals <- 0:(df$class[i] - 1)
  cbind(
    x = df$col[i],
    y = df$row[i],
    z = z_vals
  )
}))

coords <- as.data.frame(coords)

# Color palette
max_z <- max(coords$z)
pal <- terrain.colors(max_z + 1)

coords$fill <- pal[coords$z + 1]

# visualize with isocubes
g <- isocubesGrob(
  coords = coords,
  fill = coords$fill,   # outlines only
  col  = "black",
  size = 2.5,
  y = 0.25,
  angle = 30
)

# Open PNG device
img_name <- "16-cell.png"

png(img_name,
    width = 3000,   # adjust
    height = 3000,  # adjust
    res = 300,
    bg = NA)      # high-res print quality

grid.newpage()
grid.draw(g)

dev.off()

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 19

# Load required packages
library(sf)
library(lwgeom)
library(cowplot)
library(dplyr)
library(ggplot2)
library(patchwork)
library(rnaturalearth)
library(magick)

# An overview of projections here:
# https://map-projections.net/imglist.php

# Get world map
worldmap <- ne_countries(scale = 'medium', type = 'map_units',
    returnclass = 'sf')

# Get places to connect
places <- rbind(
    "Puerto Guadal" = c(-46.84587, -72.70271),
    Valdivia = c(-39.82040, -73.23898),
    Llaver<c3><ad>a = c(-34.17339, -71.32848),
    Bonn = c(50.73616, 7.10202),
    "Addis Abeba" = c(8.9919, 38.7622),
    Marigat = c(0.47189, 35.98589),
    Korogwe = c(-5.15626, 38.45412)
)

places <- data.frame(name = rownames(places), lon = places[ , 2],
    lat = places[ , 1]) %>%
    st_as_sf(coords = c("lon", "lat"), crs = 4326)

# Produce a connector
# For segmentation, see this discussion:
# https://github.com/r-spatial/sf/issues/1078

D <- 1 # segment length in degrees

link <- places %>%
    st_coordinates() %>%
    st_linestring() %>%
    st_sfc(crs = st_crs(places)) %>%
    st_as_sf() %>%
    st_set_crs(NA) %>%
    st_segmentize(dfMaxLength = D) %>%
    st_set_crs(st_crs(places))

# Produce the background
bg <- list(cbind(x = c(rep(c(-180, 180), each = 2), -180),
    y = c(-90, rep(90, 2), rep(-90, 2)))) %>%
    st_polygon() %>%
    st_sfc(crs = st_crs(places)) %>%
    st_as_sf() %>%
    st_set_crs(NA) %>%
    st_segmentize(dfMaxLength = D) %>%
    st_set_crs(st_crs(places))

# Draw projections -------------------------------------------------------------

# plain carre
water <- "#ccfcff"

p0 <- ggplot() + 
  geom_sf(data = bg, fill = water, color = NA) +
  geom_sf(data = worldmap, fill = "#b4b4b4", color = water) +
  geom_sf(data = link, color = "darkgreen") +
  geom_sf(data = places, color = "darkgreen", cex = 1.8) +
  geom_sf(data = places, color = "yellow", cex = 1) +
  theme_map() +
  theme(
    plot.title = element_text(hjust = 0.5)  # Center the title
  )

p1 <- p0 +
    ggtitle("Plate carr<c3><a9>e")
p1

# Robinson
p2 <- p0 +
    coord_sf(crs = st_crs("ESRI:54030")) +
    ggtitle("Robinson")
p2

# Winkel tripel
# See https://wilkelab.org/practicalgg/articles/Winkel_tripel.html

wintri <- list(crs = "+proj=wintri +datum=WGS84 +no_defs +over")

for(i in c("worldmap", "bg", "link", "places"))
    wintri[[i]] <- st_transform_proj(get(i), crs = wintri$crs)

p3 <- ggplot() +
    geom_sf(data = wintri$bg, fill = water, color = NA) +
    geom_sf(data = wintri$worldmap, fill = "#b4b4b4", color = water) +
    geom_sf(data = wintri$link, color = "darkgreen") +
    geom_sf(data = wintri$places, color = "darkgreen", cex = 1.8) +
    geom_sf(data = wintri$places, color = "yellow", cex = 1) +
    coord_sf(datum = NULL) +
    theme_map() +
    theme(
        plot.title = element_text(hjust = 0.5)  # Center the title
    ) +
    ggtitle("Winkel tripel")
p3

# Mollweide
p4 <- p0 +
    coord_sf(crs = "+proj=moll") +
    ggtitle("Mollweide")
p4

# Bonne
p5 <- p0 +
    coord_sf(crs = st_crs("ESRI:54024")) +
    ggtitle("Bonne")
p5

# Orthographic
grid <- st_make_grid(bg, cellsize = c(0.5, 1), what = "polygons")

p6 <- ggplot() + 
  geom_sf(data = grid, fill = water, color = water) +
  geom_sf(data = worldmap, fill = "#b4b4b4", color = water) +
  geom_sf(data = link, color = "darkgreen") +
  geom_sf(data = places, color = "darkgreen", cex = 1.8) +
  geom_sf(data = places, color = "yellow", cex = 1) +
  theme_map() +
  theme(
    plot.title = element_text(hjust = 0.5),  # Center the title
  ) +
  coord_sf(crs = "+proj=ortho") +
  ggtitle("Orthographic")
p6

# All plots together
all_in_one <- (p1 | p2) /
              (p3 | p4) /
              (p5 | p6)

all_in_one

# Save the map as PNG file
img_name <- "19-projections.png"
ggsave(img_name, plot = all_in_one, width = 8, height = 12, dpi = 300,
  bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 22

# Load packages
library(dplyr)
library(sf)
library(terra)
library(rnaturalearth)
library(ggplot2)
library(tidyterra)
library(cowplot)
library(RColorBrewer)
library(magick)

# Download South American map (without Falkland Islands and French Guyana)
sam <- ne_countries(continent = "South America", scale = "small", type = "map_units",
    returnclass = "sf") %>%
  filter(!adm0_iso %in% c("B12", "GUF"))

# Download capital cities
sam_caps <- ne_download(type = "populated_places", category = "cultural",
  returnclass = "sf") %>%
  filter(ADM0NAME %in% sam$admin &
      FEATURECLA == "Admin-0 capital")

# Create a raster as template
empty_rast <- rast(ext(sam), resolution = 0.1)
crs(empty_rast) <- st_crs(sam)$wkt

# Rasterized countries
sam_r <- rasterize(vect(sam), empty_rast, field = "adm0_iso")

# Empty raster to collect results
dist_r <- sam_r
values(dist_r) <- NA

for (cid in unique(sam$adm0_iso)) {
  message("Processing: ", cid)
  # subset country polygon and its capital
  r <- mask(sam_r, sam_r == cid, inverse = TRUE, maskvalues = TRUE)
  p <- sam_caps %>%
    filter(ADM0_A3 == cid)
  # distance raster inside country
  d <- distance(r, vect(p))/1000
  d <- mask(d, r)
  # write it into the output raster
  dist_r <- cover(dist_r, d)
}

# Reclassify by a distance step
steps <- 250 # in km
maxd <- as.integer(global(dist_r, "max", na.rm = TRUE)["adm0_iso", ]/steps) + 1
breaks <- c(0:maxd)*steps
breaks <- cbind(head(breaks, -1), tail(breaks, -1), seq_along(breaks[-1]))

dist_class <- classify(
  dist_r,
  rcl = breaks,
  include.lowest = TRUE,
  right = FALSE
)

# set palette
the_colors <- colorRampPalette(brewer.pal(9, "YlOrRd"))(nrow(breaks))

# Plotting map
m <- ggplot() +
  geom_spatraster(data = as.factor(dist_class)) +
  scale_fill_manual(
    values = the_colors,
    na.value = NA,
  ) +
  geom_sf_text(data = sam_caps, aes(label = NAME_ES), angle = 45) +
  theme_map() +
  theme(legend.position = "none")
m

# Save the map as PNG file
img_name <- "22-naturalearth.png"
ggsave(img_name, plot = m, width = 8, height = 10, dpi = 300,
  bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 23

# Load packages
library(osmdata)
library(sf)
library(lwgeom)
library(dplyr)
library(ggplot2)
library(patchwork)
library(magick)

# Do query to osm
bbox <- st_bbox(c(xmin = 7.012, ymin = 50.761, xmax = 7.13, ymax = 50.861), crs = 4326)

roads <- opq(bbox = bbox) %>%
  add_osm_feature(key = "highway") %>%
  osmdata_sf()

roads <- roads$osm_lines %>%
  select(osm_id, name, highway, lanes, surface)

# Get bounding box and apply coordinates transformation
bbox <- st_as_sfc(bbox) %>%
  st_as_sf() %>%
  st_transform(crs = "EPSG:25832")

roads <- roads %>%
  st_transform(crs = st_crs(bbox)) %>%
  st_crop(y = bbox)

# Plot 1: raw data
m1 <- ggplot() +
  geom_sf(data = bbox, fill = "grey", color = NA) +
  geom_sf(data = roads, col = "black") +
  labs(title = "Road network") +
  theme(
    plot.title = element_text(size = 16, face = "bold", hjust = 0.5)
  )
m1

# Split the background and calculate surface
blocks <- st_split(bbox, roads) %>%
  st_collection_extract(type = "POLYGON")
blocks$size <- as.numeric(st_area(blocks))/1000000 # Size in square kilometers

# Plot 2: histogram
m2 <- ggplot(data = blocks, aes(x = size)) +
  geom_histogram(bins = 200) +
  xlim(0, 0.05) +
  labs(title = "Size values distribution") +
  theme(
    plot.title = element_text(size = 16, face = "bold", hjust = 0.5)
  )
m2

# Setting manual cut levels
blocks <- blocks %>%
  mutate(size_class = as.factor(cut(size,
      breaks = c(0, 0.01, 0.03, 0.25, max(blocks$size)),
      labels = FALSE)))

# Produce the map
m3 <- ggplot() +
  geom_sf(data = blocks, aes(fill = size_class), color = NA) +
  scale_fill_manual(
    values = c(
      "4" = "#44bd4aff",
      "3" = "#c9ee8cff",
      "2" = "#e68e0bff",
      "1" = "#c53408ff"
    ),
    name = "Size\nCategories"  # legend title
  ) +
  theme_minimal() +
  theme(
    axis.title = element_blank()
  ) +
  labs(title = "Classified blocks") +
  theme(
    plot.title = element_text(size = 16, face = "bold", hjust = 0.5)
  )
m3

# Add cities to the map

# Some labels for References
cities <- rbind(
  Niederkassel = c(50.815267, 7.037569),
  Ranzel = c(50.832243, 7.030632),
  Rheidt = c(50.789460, 7.049923),
  Mondorf = c(50.779176, 7.066840),
  Bergheim = c(50.776256, 7.095724),
  Eschmar = c(50.792068, 7.109497),
  Kriegsdorf = c(50.808077, 7.102283),
  Uckendorf = c(50.820749, 7.064948),
  Libur = c(50.838601, 7.070700),
  Hersel = c(50.769734, 7.045189),
  Uedorf = c(50.783207, 7.032862),
  Widdig = c(50.794806, 7.024796),
  Urfeld = c(50.804954, 7.023736)
)
cities <- data.frame(name = rownames(cities), lon = cities[,2], lat = cities[,1])

# Cites to space
cities <- cities %>%
  st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
  st_transform(crs = st_crs(blocks))

# Produce map
m4 <- ggplot() +
  geom_sf(data = blocks, aes(fill = as.factor(size_class)), color = NA) +
  scale_fill_manual(
    values = c(
      "4" = "#44bd4aff",
      "3" = "#c9ee8cff",
      "2" = "#e68e0bff",
      "1" = "#c53408ff"
    ),
    name = "Size\nCategories"  # legend title
  ) +
  geom_sf_label(data = cities, aes(label = name), col = "black", alpha = 0.8) +
  theme_minimal() +
  labs(title = "Map with cities") +
  theme(
    plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
    axis.title = element_blank()
  )
m4

# All plots together
all_in_one <- (m1 | m2) /
              (m3 | m4)

# Write the image
img_name <- "23-process.png"
ggsave(img_name, plot = all_in_one, height = 20, width = 16, dpi = 300, bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 24

# Load packages
library(dplyr)
library(stringr)
library(sf)
library(rnaturalearth)
library(ggplot2)
library(ggrepel)
library(magick)

# Download map of Chile
chile <- ne_countries(country = "Chile", scale = "medium", returnclass = "sf") |> 
  #st_make_valid() |> 
  st_crop(y = st_bbox(c(xmin = -77, ymin = -57, xmax = -65, ymax = -17)))

# Places listed manually
places <- c(
  #"Ahinco",
  "Antuco;-37.178;-71.620;Antu ko, agua de sol.",
  #"Arauco",
  "Batuco;-33.2238;-70.8062;Vatruko, agua de totora.",
  "Butaco;-37.74846;-72.64778;Fut<c3><a1> ko, estero grande.",
  "Calbuco;-41.7616;-73.1277;Kallf<c3><bc> ko, agua azul.",
  #"Catapilco",
  "Chacabuco;-33.02799;-70.69054;Estero del chacay.",
  "Cherquenco;-38.514;-72.026;Agua del cherc<c3><a1>n.",
  "Chanco;-35.682;-72.523;Cha<c3><b1> ko, aguas de cha<c3><b1>.",
  "Choshuenco;-39.8238;-72.0834;Aguas amarillas.",
  "Coihueco;-36.642;-71.840;Koiwe ko, Agua de coihue.",
  "Coinco;-34.207;-70.941;Coy<c3><ba>n ko, agua del arenal.",
  #"Colaco",
  "Collico;-39.7911;-73.2033;Aguas rojas.",
  "Coltauco;-34.007;-71.125;Koltraw ko, agua de renacuajos.",
  "Culenc<c3><b3>;-36.7086;-72.7377;Agua del cul<c3><a9>n.",
  #"Cumeico;;;Kumei ko, aguas buenas.",
  "Cunco;-38.9039;-72.0374;Agua clara.",
  "Curic<c3><b3>;-34.9580;-71.2395;Aguas negras.",
  #"Curi<c3><b1>anco",
  "Gualleco;-35.147;-71.955;Walleko, agua de hualles.",
  #"Huasco",
  "Lanco;-39.44746;-72.77636;Aguas tranquilas.",
  "Llico;-34.76566;-72.07456;Lli ko, desaguadero.",
  "Lumaco;-38.16415;-72.90630;Luma ko, agua de luma.",
  #"Malleco;;;Agua gredosa.",
  "Malloco;-33.60418;-70.85152;Mallo ko, agua de greda dorada.",
  "Melipeuco;-38.780;-71.675;Encuentro de cuatro aguas.",
  "Nantoco;-27.5241;-70.2679;Agua de pozo.",
  "Paillaco;-40.0576;-72.8709;Pailla ko, aguas tranquilas.",
  "Peleco;-36.96265;-72.67439;Peleko, agua con barro.",
  #"Pemuco",
  "Penco;-36.73434;-72.99368;Pen ko, divisando el agua.",
  "Perquenco;-38.376;-72.375;Perken ko, agua sanadora.",
  "Quidico;-38.24640;-73.48566;Agua solitaria.",
  "Quilaco;-37.68165;-72.00542;Quila ko, tres r<c3><ad>os.",
  "Ranco;-40.32063;-72.48148;Agua con oleaje.",
  "Renaico;-37.6645;-72.5832;Agua de cueva.",
  "Temuco;-38.7380;-72.5949;Temu ko, Agua de temu."
)

# Transform to data frame
places <- as.data.frame(do.call(rbind, str_split(places, pattern = ";")))
names(places) <- c("name", "latitude", "longitude", "description")

# Transform to sf
places <- places |>
  st_as_sf(coords = c("longitude", "latitude"), crs = 4326)

# Add coordinates to the data frame for ggrepel
places <- places |> 
  mutate(X = st_coordinates(places)[ , 1], Y = st_coordinates(places)[ , 2])

# Do the plot
m <- ggplot() +
  geom_sf(data = chile, fill = "gray", color = NA) +
  geom_sf(data = places) +
  geom_text_repel(data = places, aes(x = X, y = Y, label = name), size = 4,
      max.overlaps = 100) +
  ylim(-42, -27) +
  theme(
    axis.title = element_blank(),
    axis.text.x = element_text(angle = 90)
  )
m

# Save the map as PNG file
img_name <- "24-names.png"
ggsave(img_name, plot = m, width = 8, height = 10, dpi = 300,
  bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 27

# Load packages
library(sf)
library(dplyr)
library(rmapshaper)
library(rnaturalearth)
library(units)
library(ggplot2)
library(RColorBrewer)
library(magick)

# Download South American map
sam <- ne_countries(continent = "South America", scale = "large",
    returnclass = "sf") |> 
  st_crop(y = c(xmin = -83.6, ymin = -57, xmax = -32.8, ymax = 13.25)) |>
  st_cast("POLYGON") |> 
  st_make_valid() |> 
  mutate(pid = row_number())

# Select only polygons with neighbours
sam_n <- sam |> 
  mutate(neighbours = lengths(st_touches(sam))) |> 
  filter(neighbours != 0)

# Get pairs
pairs <- st_touches(sam_n)

pairs_l <- list()
for(i in seq_along(pairs))
  pairs_l[[i]] <- cbind(i, pairs[[i]])
pairs_l <- do.call(rbind, pairs_l)
for (i in seq_along(pairs_l[ , 1]))
  pairs_l[i, ] <- pairs_l[i, order(pairs_l[i, ])]
pairs_l <- unique(as.data.frame(pairs_l))

# convert to pid
for (i in seq_along(pairs_l[1, ]))
  pairs_l[ , i] <- sam_n$pid[match(pairs_l[ , i], 1:nrow(sam_n))]

# Extract Borders Pairwise
sam_borders <- list()

for(i in seq_along(pairs_l[ , 1]))
  sam_borders[[i]] <- sam_n  |>
    filter(sam_n$pid %in% unlist(pairs_l[i, ])) |> 
    ms_simplify(keep = .2, keep_shapes = TRUE) |> 
    ms_innerlines() |> 
    as_tibble() |> 
    st_as_sf() |> 
    mutate(p1 = pairs_l[i, 1], p2 = pairs_l[i, 2])

sam_borders <- do.call(rbind, sam_borders) |> 
  mutate(lid = row_number()) |> 
  mutate(
    ctr1 = sam_n$adm0_a3[match(p1, sam_n$pid)],
    ctr2 = sam_n$adm0_a3[match(p2, sam_n$pid)]) |> 
  mutate(group = paste(ctr1, ctr2, sep = ".")) |> 
  group_by(group) |> 
  summarise(geometry = st_cast(st_combine(geometry), "MULTILINESTRING"))

# Add length and new identifier
sam_borders <- sam_borders |> 
  mutate(length = as.numeric(st_length(sam_borders))/1000, lid = row_number()) |> 
  filter(!grepl("SPI", group))

# Overview of borders
ggplot(data = sam_borders) +
  geom_sf(data = sam) +
  geom_sf(data = sam_borders, color = "red") +
  facet_wrap(~lid)

# Map with coloured lines according to their extension

continent <- "#770000ff"

m <- ggplot(data = sam_borders, aes(color = length)) +
  geom_sf(data = sam, fill = continent, color = continent) +
  geom_sf(data = sam_borders, size = 0.8) +
  scale_color_gradientn(colors = rev(brewer.pal(9, "YlOrRd")), name = "Length\n(km)") +
  theme_minimal()
m

# Save the map as PNG file
img_name <- "27-boundaries.png"
ggsave(img_name, plot = m, height = 9, width = 6, dpi = 300, bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)

Day 30

# Load packages
library(dplyr)
library(stringr)
library(sf)
library(elevatr)
library(terra)
library(tidyterra)
library(rnaturalearth)
library(ggplot2)
library(ggrepel)
library(cowplot)
library(magick)

# Places listed manually
places <- c(
  # CHILE --------------------------------------------------------------------------------
  #"Ahinco",
  "Antuco;-37.178;-71.620;Antu ko, agua de sol.",
  #"Arauco",
  "Batuco;-33.2238;-70.8062;Vatruko, agua de totora.",
  "Batuco;-31.9347;-70.6119;",
  "Butaco;-37.74846;-72.64778;Fut<c3><a1> ko, estero grande.",
  "Calbuco;-41.7616;-73.1277;Kallf<c3><bc> ko, agua azul.",
  "Calbuco;-40.412769;-72.721596;",
  #"Catapilco",
  "Chacabuco;-33.02799;-70.69054;Chacay bucu, Estero del chacay.",
  "Cherquenco;-38.514;-72.026;Agua del cherc<c3><a1>n.",
  "Chanco;-35.682;-72.523;Cha<c3><b1> ko, aguas de cha<c3><b1>.",
  "Chanco;-42.771;-73.718;",
  "Chanco;-40.30964;-72.19957;",
  "Chanco;-38.275;-72.946;",
  "Chanco;-39.95785;-72.74837;",
  "Chanco;-40.42323;-73.32567;",
  "Choshuenco;-39.8238;-72.0834;Aguas amarillas.",
  "Coihueco;-36.642;-71.840;Koiwe ko, Agua de coihue.",
  "Coihueco;-38.07828;-73.30915;",
  "Coihueco;-38.42747;-71.89427;",
  "Coihueco;-40.9654;-73.2733;",
  "Coihueco;-35.78819;-72.43655;",
  "Coihueco;-38.50277;-72.57671;",
  "Coinco;-34.207;-70.941;Coy<c3><ba>n ko, agua del arenal.",
  #"Colaco",
  "Collico;-39.7911;-73.2033;Aguas rojas.",
  "Collico;-37.97377;-73.41940;",
  "Collico;-39.34333;-72.21987;",
  "Collico;-38.48104;-71.85265;",
  "Collico;-39.89258;-72.79232;",
  "Collico;-40.43421;-72.75288;",
  "Collico;-38.08120;-72.41110;",
  "Collico;-39.14893;-73.13247;",
  "Collico;-39.41862;-72.69306;",
  "Coltauco;-34.007;-71.125;Koltraw ko, agua de renacuajos.",
  "Culenc<c3><b3>;-34.79118;-71.69583;Agua del cul<c3><a9>n.",
  "Culenco;-36.72606;-72.73790;",
  "Culenco;-37.40616;-72.79562;",
  "Culenco;-36.12973;-72.57693;",
  "Culenco;-37.00999;-72.18151;",
  "Culenco;-36.30579;-72.28064;",
  #"Cumeico;;;Kumei ko, aguas buenas.",
  "Cunco;-38.9039;-72.0374;Agua clara.",
  "Cunco;-41.80216;-73.37734;",
  "Curic<c3><b3>;-34.9580;-71.2395;Aguas negras.",
  #"Curi<c3><b1>anco",
  "Gualleco;-35.147;-71.955;Walleko, agua de hualles.",
  #"Huasco",
  "Lanco;-39.44746;-72.77636;Aguas tranquilas.",
  "Llico;-34.76566;-72.07456;Lli ko, desaguadero.",
  "Llico;-37.19875;-73.56531;",
  "Lumaco;-38.16415;-72.90630;Luma ko, agua de luma.",
  "Lumaco;-39.714796;-72.438102;",
  "Lumaco;-39.06325;-72.70522;",
  "Lumaco;-39.98046;-73.28662;",
  "Lumaco;-40.01605;-72.79103;",
  #"Malleco;;;Agua gredosa.",
  "Malloco;-33.60418;-70.85152;Mallo ko, agua de greda dorada.",
  "Melipeuco;-38.780;-71.675;Encuentro de cuatro aguas.",
  #"Nantoco;-27.5241;-70.2679;Agua de pozo.",
  "Paillaco;-40.0576;-72.8709;Pailla ko, aguas tranquilas.",
  "Paillaco;-40.01605;-72.79103;",
  "Peleco;-36.96265;-72.67439;Peleko, agua con barro.",
  "Peleco;-37.87008;-73.37811;",
  #"Pemuco",
  "Penco;-36.73434;-72.99368;Pen ko, divisando el agua.",
  "Perquenco;-38.376;-72.375;Perken ko, agua sanadora.",
  "Quidico;-38.24640;-73.48566;Agua solitaria.",
  "Quidico;-37.35436;-73.60080;",
  "Quilaco;-37.68165;-72.00542;Quila ko, tres r<c3><ad>os.",
  "Quilaco;-37.35436;-73.60080;Agua de quila.",
  "Quilaco;-36.70627;-72.70984;",
  "Quilaco;-38.4124;-72.6272;",
  "Quilaco;-38.05237;-73.04318;",
  "Quilaco;-38.13481;-72.40022;",
  "Ranco;-40.32063;-72.48148;Agua con oleaje.",
  "Renaico;-37.6645;-72.5832;Agua de cueva.",
  "Temuco;-38.7380;-72.5949;Temu ko, Agua de temu.",
  # ARGENTINA ----------------------------------------------------------------------------
  "Angaco;-31.1893;-68.1139;Corrientes.",
  "Atreuc<c3><b3>;-37.1275;-63.8144;Aguas malas (salobres).",
  "Atreuco;-39.73010;-71.07828;",
  "Chapalc<c3><b3>;-38.64456;-63.09620;Agua barrosa.",
  "Claromec<c3><b3>;-38.85134;-60.06775;Tres arroyos.",
  "Comic<c3><b3>;-41.071336;-67.476976;Aguas escondidas.",
  "Cuchillo C<c3><b3>;-38.333333;-64.616667;Agua de chuchillos.",
  "Huinca Renanc<c3><b3>;-34.84107;-64.37490;Pozo de agua del cristiano.",
  "Huinganco;-37.15797;-70.62117;Agua de huing<c3><a1>n.",
  "Leubuc<c3><b3>;-36.87205;-63.03994;Lewf<c3><bc> ko, aguada del r<c3><ad>o.",
  #"Mach<c3><b3>nico;-40.332581;-71.417677;Lugar de p<c3><a1>ncora.",
  "Naic<c3><b3>;-36.92368;-64.40477;Manantial que baja.",
  "<c3><91>orquinc<c3><b3>;-41.8491;-70.9020;Agua de <c3><b1>orquin.",
  "Pehuen-C<c3><b3>;-39.002;-61.545;Agua de pehu<c3><a9>n.",
  "Realic<c3><b3>;-35.03752;-64.24633;Agua en forma de plato."
)

# Transform to data frame
places <- as.data.frame(do.call(rbind, str_split(places, pattern = ";")))
names(places) <- c("name", "latitude", "longitude", "description")

# Transform to sf
places <- places |>
  st_as_sf(coords = c("longitude", "latitude"), crs = 4326)

# Add coordinates to the data frame for ggrepel
places <- places |> 
  mutate(X = st_coordinates(places)[ , 1], Y = st_coordinates(places)[ , 2])


# Define bounding box
bbox <- c(xmin = -77, xmax = -58, ymin = -43.5, ymax = -30.5)

# Load DEM
dem <- get_elev_raster(locations = st_sf(st_as_sfc(st_bbox(bbox, crs = 4326))),
    z = 7, clip = "locations")

# Load continent
sam <- ne_countries(continent = "South America", scale = "large",
    returnclass = "sf") |> 
  #filter(name %in% c("Chile", "Argentina")) |> 
  st_make_valid() |> 
  group_by(continent) |> 
  summarise(geometry = st_union(geometry))

# Mask dem with polygon
dem_masked <- mask(dem, sam)

# Calculate Hillshades
slope <- terrain(rast(dem_masked), "slope", unit = "radians")
aspect <- terrain(rast(dem_masked), "aspect", unit = "radians")
hill <- shade(slope, aspect, 30, 270)

# normalize names
#names(hill) <- "shades"

# Hillshading, but we need a palette
pal_greys <- hcl.colors(1000, "Grays")

# Main Map
m <- ggplot() +
  geom_sf(data = st_sf(st_as_sfc(st_bbox(bbox, crs = 4326))), fill = "#364891ff",
    color = NA) +
  geom_spatraster(data = hill, show.legend = FALSE) +
  scale_fill_gradientn(colors = pal_greys, na.value = NA) +
  geom_spatraster(data = rast(dem_masked), alpha = 0.7, show.legend = FALSE) +
  # scale_fill_gradientn(colors = grad_hypso, na.value = NA) +
  geom_sf(data = places, color = "white") +
  geom_text_repel(data = places, aes(x = X, y = Y, label = name),
    color = "white", size = 4, max.overlaps = 100) +
  xlim(bbox["xmin"], bbox["xmax"]) +
  ylim(bbox["ymin"], bbox["ymax"]) +
  theme(
    axis.title = element_blank(),
    axis.text.x = element_text(angle = 90)
  ) +
  theme_map()
m

# Minimap
bbox2 <- c(xmin = -82, xmax = -32, ymin = -60, ymax = -10)

mm <- ggplot() +
  # geom_sf(data = st_sf(st_as_sfc(st_bbox(bbox2, crs = 4326))), fill = "#364891ff",
  #   color = NA) +
  geom_sf(data = sam, fill = "gray", color = NA) +
  geom_sf(data = st_sf(st_as_sfc(st_bbox(bbox, crs = 4326))), fill = NA, color = "yellow",
      size = 1) +
  xlim(bbox2["xmin"], bbox2["xmax"]) +
  ylim(bbox2["ymin"], bbox2["ymax"]) +
  theme_map() +
  theme(
    panel.background = element_rect(fill = "#364891ff", color = NA)
  )
mm

# Final image
map <- ggdraw(m)  +
          draw_plot(mm, x = 0.72, y = 0.64, width = 0.2, height = 0.2)
map

# Save the map as PNG file
img_name <- "30-makeover.png"
ggsave(img_name, plot = map, width = 10, height = 10, dpi = 300,
  bg = "transparent")

# Trim transparent edges
img <- image_trim(image = image_read(img_name))
image_write(image = img, path = img_name)