Section 22 Overview Maps

This code is based off a script provided by Christoph Schürz. It provides overview maps of our catchment meant for reporting. They are adjusted to fit our CS.

# -------------------------------------------------------------------------
# Script for catchment overview plots and buildR setup properties ---------
# Version 0.1
# Date: 2024-02-29
# Developer: Christoph Schürz christoph.schuerz@ufz.de
# -------------------------------------------------------------------------
require(tmap)
require(terra)
require(sf)
require(rcartocolor)
require(tidyverse)


# Shows all point shapes, adapted from the ggpubr package
show_point_shapes <- function () {
  d <- data.frame(p = c(0:25))
  p <- ggplot(data = d, aes(x = p%%6, y = p%/%6, shape = p, label = p)) + 
    scale_shape_identity() + 
    geom_point(size = 5, fill = "red1") + 
    geom_text(aes(y = p%/%6 + 0.25), 
              size = 3) + 
    scale_y_reverse() + 
    theme_void()
  return(p)
}

Settings:

# Settings ----------------------------------------------------------------
## Define path to your buildR project folder
#buildr_path <- 'Y:/Gruppen/optain/3. Case Studies/1 DEU - Schwarzer Schöps/SWAT_setup/schoeps_230207'
buildr_path <- "model_data/cs10_setup/optain-cs10/"
# Define the path where the output figure and the model setup properties should be saved
#output_path <- 'Y:/Gruppen/optain/3. Case Studies/1 DEU - Schwarzer Schöps/SWAT_setup/schoeps_230207'
output_path <- "model_data/results"
dir.create(output_path, showWarnings = F)

#point_source_path   <- paste0(buildr_path, '/input_data/point_source/pnts_reichenbach.shp')
point_source_path   <- 'model_data/input/pointsource/cs10-ps-data.shp'
# Load and prepare vector layers from the buildR project ------------------
## Basin boundary
basin   <- read_sf(paste0(buildr_path, '/data/vector/basin.shp'))
## Channel layer
channel <- read_sf(paste0(buildr_path, '/data/vector/cha.shp')) %>% 
  mutate(type = 'channel')
## Reservoir layer
res     <- read_sf(paste0(buildr_path, '/data/vector/res.shp')) %>% 
  mutate(type = 'reservoir')
## HRU layer (which excludes already the water areas)
## A variable 'type_group' is generated which excludes the numbers at the
## endings of names, so that only the main group name remains 
## (e.g. for 'field_1234' the 'type_group' is 'field')
hru <- read_sf(paste0(buildr_path, '/data/vector/hru.shp')) %>% 
  mutate(type_group = str_remove(type, '_[:digit:]+$'))

## Prepare the land layer for plotting ------------------------------------
## The reservoir layer is copied as a water layer and a type_group 'watr' 
## is added
water <- res %>% 
  select() %>% 
  mutate(type_group = 'watr')

## The land layer is the combination the HRUs 'type_group's and the water
## areas 
land <- bind_rows(hru, water)

## Check which 'type_group's are present in your setup. You have to define
## a label and a plot color for each.
unique(land$type_group)
##   [1] "frst"   "rngb"   "urml"   "a_001f" "a_002f" "utrn"   "a_003f" "a_004f"
##   [9] "a_005f" "a_006f" "a_007f" "a_008f" "a_009f" "a_010f" "a_011f" "a_012f"
##  [17] "past"   "a_013f" "a_014f" "a_015f" "a_016f" "a_017f" "a_018f" "a_019f"
##  [25] "a_020f" "a_021f" "a_022f" "a_023f" "a_024f" "a_025f" "a_026f" "a_027f"
##  [33] "a_028f" "a_029f" "a_030f" "a_031f" "a_032f" "a_033f" "a_034f" "a_035f"
##  [41] "a_036f" "a_037f" "a_038f" "a_039f" "a_040f" "a_041f" "a_042f" "a_043f"
##  [49] "a_044f" "a_045f" "a_046f" "a_047f" "a_048f" "a_049f" "a_050f" "a_051f"
##  [57] "a_052f" "a_053f" "a_054f" "a_055f" "a_056f" "a_057f" "a_058f" "a_059f"
##  [65] "a_060f" "a_061f" "a_062f" "a_063f" "a_064f" "a_065f" "a_066f" "a_067f"
##  [73] "a_068f" "a_069f" "a_070f" "a_071f" "a_072f" "a_073f" "a_074f" "a_075f"
##  [81] "a_076f" "a_077f" "a_078f" "a_079f" "a_080f" "a_081f" "a_082f" "a_083f"
##  [89] "a_084f" "a_085f" "a_086f" "a_087f" "a_088f" "a_089f" "a_090f" "a_091f"
##  [97] "a_092f" "a_093f" "a_094f" "a_095f" "a_096f" "a_097f" "a_098f" "a_099f"
## [105] "a_100f" "a_101f" "a_102f" "a_103f" "a_104f" "a_105f" "a_106f" "a_107f"
## [113] "a_108f" "a_109f" "a_110f" "wetf"   "a_111f" "a_112f" "a_113f" "a_114f"
## [121] "a_115f" "a_116f" "a_117f" "a_118f" "a_119f" "a_120f" "a_121f" "a_122f"
## [129] "a_123f" "a_124f" "a_125f" "a_126f" "a_127f" "a_128f" "a_129f" "a_130f"
## [137] "a_131f" "a_132f" "a_133f" "a_134f" "a_135f" "a_136f" "a_137f" "a_138f"
## [145] "a_139f" "a_140f" "a_141f" "a_142f" "a_143f" "a_144f" "a_145f" "a_146f"
## [153] "a_147f" "a_148f" "a_149f" "a_150f" "a_151f" "a_152f" "a_153f" "a_154f"
## [161] "a_155f" "a_156f" "a_157f" "a_158f" "a_159f" "a_160f" "a_161f" "a_162f"
## [169] "a_163f" "a_164f" "a_165f" "a_166f" "a_167f" "a_168f" "a_169f" "a_170f"
## [177] "a_171f" "a_172f" "a_173f" "a_174f" "a_175f" "a_176f" "a_177f" "a_178f"
## [185] "a_179f" "a_180f" "a_181f" "a_182f" "a_183f" "a_184f" "a_185f" "a_186f"
## [193] "a_187f" "a_188f" "a_189f" "a_190f" "a_191f" "a_192f" "a_193f" "a_194f"
## [201] "a_195f" "a_196f" "a_197f" "a_198f" "a_199f" "a_200f" "a_201f" "a_202f"
## [209] "a_203f" "a_204f" "a_205f" "a_206f" "a_207f" "a_208f" "a_209f" "a_210f"
## [217] "a_211f" "a_212f" "a_213f" "a_214f" "a_215f" "a_216f" "a_217f" "a_218f"
## [225] "a_219f" "a_220f" "a_221f" "a_222f" "watr"

Clearly, we need to change our unique fields to one “class”

agrifield_index <- grepl(x=land$type_group, pattern = "a_") %>% which()

land$type_group[agrifield_index] <- "field"
unique(land$type_group)
## [1] "frst"  "rngb"  "urml"  "field" "utrn"  "past"  "wetf"  "watr"
## Define the plot colors and the labels in the map legend for each
## 'type_group'. To have comparable maps it is advisable to use the 
## same colors for the respective land uses in each case study map.
land_colors <- tribble(~type_group, ~label,                      ~color,
                       'field',     'Cropland',                  '#ffe18c',
                       'past',      'Meadows',                   '#33a02c', 
                       'frst',      'Forest',                    '#4e6c39', 
                       'rngb',      'Shrubs, semi-natural',      '#838245',
                       'urml',      'Urban, moderate density',   '#e8718d',
                       'utrn',      'Urban, transport',          '#c31619', 
                       'wetf',      'Wetlands',                  '#d945b7', 
                       'watr',      'Water areas',               '#5394A9'
)

## Check if each 'type_group' was defined in the 'land_colors' table./
if(!all(unique(land$type_group %in% land_colors$type_group))) {
  stop("Please define all 'type_group's from the layer 'land' in 'land_colors'!")
} else {
  land$type_group <- factor(land$type_group, 
                            levels = land_colors$type_group, 
                            labels = land_colors$label)
}

# Load and prepare raster layers from the buildR setup --------------------
# The dem and soil raster layers can be rather large and would be downsampled
# during the plotting to 10^6 pixels by the plot function. Therefore, the
# layers are aggregated to about that number of pixels before plotting to 
# save time in the plot process. If this was done once the aggregated layers
# are saved in the buildR project's raster data and are loaded if 

if(!file.exists(paste0(buildr_path, '/data/raster/hillshade.tif'))) {
  
  dem <- rast(paste0(buildr_path, '/data/raster/dem.tif'))
  
  dim_dem <- dim(dem)
  fct_agg <- round(sqrt(dim_dem[1]*dim_dem[2]/1e6))
  
  dem_agg <- aggregate(dem, fct_agg) %>% 
    crop(., vect(basin), mask = TRUE)
  
  slope <- terrain(dem_agg, "slope", unit="radians")
  aspect <- terrain(dem_agg, "aspect", unit="radians")
  hill <- shade(slope, aspect, 40, 270)
  
  soil <- rast(paste0(buildr_path, '/data/raster/soil.tif'))
  soil <- as.factor(soil)
  
  soil_agg <- aggregate(soil, fct_agg) %>% 
    crop(., vect(basin), mask = TRUE)
  
  writeRaster(hill, paste0(buildr_path, '/data/raster/hillshade.tif'))
  writeRaster(dem_agg, paste0(buildr_path, '/data/raster/dem_agg.tif'))
  writeRaster(soil_agg, paste0(buildr_path, '/data/raster/soil_agg.tif'))
} else {
  hill <- rast(paste0(buildr_path, '/data/raster/hillshade.tif'))
  dem_agg <- rast(paste0(buildr_path, '/data/raster/dem_agg.tif'))
  soil_agg <- rast(paste0(buildr_path, '/data/raster/soil_agg.tif'))
}

Prepping our MetNord Dataset for this script:

metadata <- read_csv("model_data/input/met/reanalysis3_metadata.csv", show_col_types = F)

projcrs <- "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"

stations <- sf::st_as_sf(x = metadata, coords = c("Long", "Lat"), crs = projcrs)


stations$type <- "placeholder"
write_sf(stations, "model_data/input/met/stations_spatial.shp")


slr_station <- data.frame(type = "slr", Lat = 59.6606132414636, Long = 10.7823462360971)
slr_station <- sf::st_as_sf(x = slr_station, coords = c("Long", "Lat"), crs = projcrs)
write_sf(slr_station, "model_data/input/met/slr_station.shp")

mapview::mapview(stations, zcol = "Elevation")+mapview::mapview(slr_station, zcol = "type")
# Load additional input layers --------------------------------------------
# 
# Weather data
# The example of weather data (how they are organized in CS1) shows how to 
# prepare the data for plotting in the overview maps.
# 
# The workflow is always as follows:
# - Load the individual point shape layers
# - Add a column 'type' which defines which variable it is
# - bind the point layers together
# - define a point settings table, to link the 'type' with a full label which 
# is printed in the figures legend, the point shape, and point colors.
# 
# You can customize the code to your data for the weather and the gauge data.
# 
# You can get an overview of the available point shapes with the function 
# show_point_shapes() to get their corresponding values.

# Load the individual point shape layers
  ## Read the shape file
pcp <- read_sf( "model_data/input/met/stations_spatial.shp")


# tmp <- read_sf(paste0(weather_path, '/', 'tmp.shp')) %>% 
#   mutate(type = 'tmp') %>% 
#   select(type)
# rh  <- read_sf(paste0(weather_path, '/', 'rh.shp')) %>% 
#   mutate(type = 'rh') %>% 
#   select(type)
solar <- read_sf("model_data/input/met/slr_station.shp")


# wind <- read_sf(paste0(weather_path, '/', 'wind.shp')) %>% 
#   mutate(type = 'wind') %>% 
#   select(type)

# Bind the layers together 
#weather <- bind_rows(pcp, tmp, rh, solar, wind)
weather <- bind_rows(pcp, solar)

weather_point_settings <- tribble( ~type,   ~label,               ~shape, ~color,
                                   'placeholder',   'Precipitation,',          22,  'azure2',
                                   'tmp',   'Temperature,',            NA,  'green4',
                                   'rh',    'Relative humidity,',      NA,  'red3',
                                   'wind',  'Wind speed',             NA,  'pink3',
                                   'slr',   'Solar radiation',        22,  'darkgoldenrod1'
                                  )


if(!all(unique(weather$type %in% weather_point_settings$type))) {
  stop("Please define all 'type's from the layer 'weather' in 'weather_point_settings'!")
} else {
  weather$type <- factor(weather$type, 
                         levels = weather_point_settings$type,
                         labels = weather_point_settings$label)
}

## Gauge data
## For the gauge data please follow the same approach to customize the plot and
## the plot legend.
flow <- read_sf("model_data/input/point/cs10_outlet_utm33n.shp")

flow$type = "flow"

gauges <- bind_rows(flow)#,  ncon, pcon)

gauge_point_settings <- tribble(~type,   ~label,         ~shape, ~color,
                                 'flow',  'Outlet',       25,  'royalblue3', 
                                 # 'ncon',  'Nitrate',         21,  'yellow',
                                 # 'pcon',  'Phosphorous',     23,  'brown',
)

if(!all(unique(gauges$type %in% gauge_point_settings$type))) {
  stop("Please define all 'type's from the layer 'weather' in 'gauge_point_settings'!")
} else {
  gauges$type <- factor(gauges$type, 
                        levels = gauge_point_settings$type,
                        labels = gauge_point_settings$label)
}

# Load point shape layer of point sources
point_sources <- read_sf(point_source_path) %>% 
  mutate(type = 'Point source') %>% 
  select(type)

Generating the maps:

# Plotting maps -----------------------------------------------------------
## Plot options for tmap
## It is recommended to keep most of the options as they were defined. 
## It may be necessary to change the legend positions or the aspect ratio (asp)
## if the legends overlap with the maps:
## - The legend position is defined by two arguments. The first is either 
##   'left' or 'right', the second is 'top' or 'bottom'. 
##   Capital letters move the legend even further to the plot margin.
## - The default of asp is set to 1 to have square plot panels. Values > 1 
##   generate wider plot panels and can give the legend more space but generates
##   a smaller map.
tmap_options(title.size = 1, # Size of a), b), c), d) (relative value)
             legend.text.size = 0.5, # Size of labels in legend (relative value)
             legend.title.size = 0.6, # Size of legend title (relative value)
             legend.title.fontface = 'bold', # Legend titles are bold
             legend.position = c('RIGHT', 'bottom'), # Legend position, here right and bottom (All caps e.g. push it to the VERY right margin)
             frame = F, # Plot panels are plotted without frames
             asp = 1, # Aspect ratio of plot panels, now plotted squared. Change if place for legend is too narrow. value > 1 = plot wider
             max.categories = Inf) # Ignore this

## Define the intervals of the scale bar, depending on the catchment size.
scale_bar_intervals <- c(0, 2.5, 5) #km


## Plot for the locations of weather stations, gauges, and point source locations.
stat_map <- tm_shape(basin) +
  tm_borders() +
  
  tm_shape(channel) +
  tm_lines(col = carto_pal(7, 'Teal')[7], legend.col.show = F) +
  
  tm_shape(res) +
  tm_polygons(col = carto_pal(7, 'Teal')[4], border.col = carto_pal(7, 'Teal')[7]) +
  
  tm_shape(point_sources) +
  tm_symbols(col = 'red2', shape = 4, size = 0.05, # border.col = 'yellow2',
             # palette = gauge_point_settings$color, 
             # shapes = gauge_point_settings$shape,
             just = c('center', 'center'), 
             legend.col.show = F, 
             legend.shape.show = F) +
  
  tm_shape(gauges) +
  tm_symbols(col = 'type', shape = 'type', border.col = 'black', size = 0.15,
             palette = gauge_point_settings$color,
             shapes = gauge_point_settings$shape,
             just = c('center', 'center'),
             legend.col.show = F,
             legend.shape.show = F) +

  tm_shape(weather) +
  tm_symbols(col = 'type', shape = 'type', size = 0.05,
             palette = weather_point_settings$color,
             shapes = weather_point_settings$shape,
             just = c('center', 'center'),
             legend.col.show = F,
             legend.shape.show = F) +
  tm_add_legend(title = "")+
  tm_add_legend(type = 'symbol',
                col = weather_point_settings$color,
                shape = weather_point_settings$shape,
                labels = weather_point_settings$label,
                size = 0.25,
                title = 'Weather') +
  tm_add_legend(type = 'line', 
                col = carto_pal(7, 'Teal')[7],
                labels = 'Channels', 
                size = 0.8,
                title = 'Water') +
    tm_add_legend(type = 'symbol',
                col = gauge_point_settings$color,
                shape = gauge_point_settings$shape,
                labels = gauge_point_settings$label,
                size = 0.4) +
  tm_add_legend(type = 'fill', 
                col = carto_pal(7, 'Teal')[4],
                border.col = carto_pal(7, 'Teal')[7],
                size = 0.4,
                labels = 'Reservoir, Pond') +
  tm_add_legend(type = 'symbol', 
                col = 'red2',
               # border.col = 'yellow2',
                shape = 4,
                size = 0.4,
                labels = 'Point source', 
                title = 'Point sources') +
  tm_layout(title = 'a)')

# Plot of the DEM and hillshade
dem_map <- tm_shape(hill) +
  tm_raster(palette = gray(0:100 / 100), n = 100, legend.show = FALSE)  +
  tm_shape(dem_agg) +
  tm_raster(alpha = 0.3, palette = terrain.colors(25), 
            style = 'cont', title = 'Elevation (m a.s.l)', 
            legend.show = T, legend.is.portrait = T, legend.reverse = T) +
  tm_shape(basin) +
  tm_borders() +
  tm_layout(title = 'b)')

# Plot of the land use layer
land_map <- tm_shape(basin) +
  tm_borders() +
  tm_shape(land) +
  tm_polygons(col = 'type_group', border.col = carto_pal(7, 'Teal')[7], 
              palette = land_colors$color, legend.show = T, lwd = 0.05, 
              title = 'Land use / cover') +
  tm_layout(title = 'c)')

# Plot of the soil map
n_soil <- nrow(unique(soil_agg))
soil_col <- colorRampPalette(carto_pal(12, "Safe"))

soil_map <- tm_shape(soil_agg) +
  tm_raster(legend.show = F, palette = soil_col(n_soil), style = 'cat') + 
  tm_shape(basin) +
  tm_borders() +
  tm_compass(size = 1.5, position = 'RIGHT') +
  tm_scale_bar(breaks = scale_bar_intervals, text.size = 0.5, 
               position = 'RIGHT') +
  tm_layout(title = 'd)')

# Combine all plot panels to the overview map
overview_map <- tmap_arrange(stat_map, dem_map, land_map, soil_map, ncol = 2)

# Save the plot
tmap_save(overview_map, paste0(output_path, '/overview_map.png'), width = 9, height = 7)
## stars object downsampled to 797 by 1254 cells. See tm_shape manual (argument raster.downsample)
## stars object downsampled to 797 by 1254 cells. See tm_shape manual (argument raster.downsample)
## stars object downsampled to 797 by 1254 cells. See tm_shape manual (argument raster.downsample)
## Map saved to D:\git\swat-cs10\model_data\results\overview_map.png
## Resolution: 2700 by 2100 pixels
## Size: 9 by 7 inches (300 dpi)
print(overview_map)
## stars object downsampled to 797 by 1254 cells. See tm_shape manual (argument raster.downsample)
## stars object downsampled to 797 by 1254 cells. See tm_shape manual (argument raster.downsample)
## stars object downsampled to 797 by 1254 cells. See tm_shape manual (argument raster.downsample)