#' Grid system
#'
#' @examples
#' flownet_geometry_new()
flownet_geometry_new <- function(
x = c(0, 15, 20, 40),
y = c(0, 10, 15, 17.5),
ix = c(1, 1, 1, 2, 3, 3),
iy = c(3, 2, 1, 1, 1, 2),
bc_id = c(1, 6),
bc_edge = c(2, 2),
bc_type = c("h", "h"),
bc_value = c(20, 15),
dry = FALSE,
grid_size = 0.25
){
#generate tibble with all soil domains
dom <- tibble::tibble(
ix = ix,
iy = iy,
x0 = x[ix],
y0 = y[iy],
x1 = x[ix + 1],
y1 = x[iy + 1],
dry = dry
)
#generate tibble with all default boundary conditions (impermeable boundaries)
bc <- tibble::tibble(
id = rep(which(dom$dry == FALSE), each = 4),
edge = rep(seq(4), sum(dom$dry == FALSE)),
type = "q",
value = 0
)
#add boundary conditions for head or flow
bc$id[4*(bc_id - 1) + bc_edge] <- bc_id
bc$edge[4*(bc_id - 1) + bc_edge] <- bc_edge
bc$type[4*(bc_id - 1) + bc_edge] <- bc_type
bc$value[4*(bc_id - 1) + bc_edge] <- bc_value
#add connecting boundary conditions
for (id in 1:nrow(dom)) {
#other domain connected at the top (edge 3)
id_next <- which((dom$ix == (dom$ix[id] + 1)) & (dom$iy == dom$iy[id]))
if (length(id_next) == 1) {
bc$edge[4*(id - 1) + 3] <- 3
bc$type[4*(id - 1) + 3] <- "c"
bc$value[4*(id_next - 1) + 3] <- id_next
bc$edge[4*(id_next - 1) + 1] <- 1
bc$type[4*(id_next - 1) + 1] <- "c"
bc$value[4*(id_next - 1) + 1] <- id
}
#other domain connected at the bottom (edge 2)
id_next <- which((dom$ix == dom$ix[id]) & (dom$iy == (dom$iy[id] + 1)))
if (length(id_next) == 1) {
bc$edge[4*(id - 1) + 2] <- 2
bc$type[4*(id - 1) + 2] <- "c"
bc$value[4*(id_next - 1) + 2] <- id_next
bc$edge[4*(id_next - 1) + 4] <- 4
bc$type[4*(id_next - 1) + 4] <- "c"
bc$value[4*(id_next - 1) + 4] <- id
}
}
#return
return(list(dom = dom, bc = bc))
}
a <- flownet_geometry_new()
#' Generate tibble describing flow net problem
#'
#' @description
#' The flow net problem consists of connected rectangular domains. The
#' permabilities can be set seperately for each domain.
#'
#' @md
#' @param x0,y0 x and y position of the bottom left corner of the first domain
#' @param Lx,Ly size in x and y direction of each rectangular domain
#' @param Ly_dry thickness of any dry soil directly on top of this domain.
#' This is only used to generate the soil geometry later. This extra height
#' is only plotted when the top edge of the domain (edge 2) has a fixed
#' head as boundary condition
#' @param kx,ky permeabilities in x and y-direction. If scalars, they are set
#' equal for each domain
#' @param type1,type2,type3,type4 Type of boundary condition on each of the
#' four edges of each domain. Edges are numbered clockwise:
#' * `type1` for the left edge (negative x-face)
#' * `type2` for the top edge (positive y-face)
#' * `type3` for the right edge (positive x-face)
#' * `type4` for the bottom edge (negative y-face)
#' Possible values for the boundary condition types are:
#' * `type = "q"`: known flow (positive in inflow)
#' * `type = "h"`: known head
#' * `type = "c"`: face is connected to a face in another domain
#' @param value1,value2,value3,value4 values of boundary conditions:
#' * if `type = "q"`, `value` is the flow rate in positive direction
#' * if `type = "h"`: `value` is the known head
#' * if `type = "c"`: `value` gives the number of the domain that the face
#' is connected to (domains are numbered 1, 2, 3, in order of appearance)
#' @param grid_size requested finite difference grid size. The grid size used
#' may vary slighly per domain, in order to fit a integer number of nodes in
#' the domain with a given size
#' @param nodes_min the minimum number of real nodes in any direction in any
#' domain. Set to at least 3 to allow all finite difference approximations
#' to work
#' @importFrom rlang .data
#' @return a tibble describing the entire tibble. In addition to the input,
#' fields are added for `x0` and `y0` (the x,y position of the bottom left)
#' point in each domain, `nx` and `ny` (the number of real nodes in each
#' domain in each direction), and `hx`, and `hy` (the distance between
#' nodes in each direction)
#' @examples
#' #create the default flow net
#' generate_flownet_properties()
#' @export
generate_flownet_properties <- function(
x0 = 0,
y0 = 0,
Lx = c(15, 15, 5, 20, 20),
Ly = c(7.5, 10, 10, 10, 5),
Ly_dry = c(0, 0, 0, 0, 0),
kx = 1e-6,
ky = 1e-6,
type1 = c("q","q","c","c","q"),
type2 = c("h","c","q","c","h"),
type3 = c("q","c","c","q","q"),
type4 = c("c","q","q","q","c"),
value1 = c(0, 0, 2, 3, 0),
value2 = c(20, 1, 0, 5, 18),
value3 = c(0, 3, 4, 0, 0),
value4 = c(2, 0, 0, 0, 4),
grid_size = 0.25,
nodes_min = 3
){
#create tibble with all input properties
df <- tibble::tibble(
Lx = Lx,
Ly = Ly,
Ly_dry = Ly_dry,
kx = kx,
ky = ky,
type1 = type1,
type2 = type2,
type3 = type3,
type4 = type4,
value1 = value1,
value2 = value2,
value3 = value3,
value4 = value4,
)
#add grid sizes
# * nx,ny: the number of grid nodes in x and y-directions
# * hx,hy: the distance between grid points in x and y-directions
# * ntotal: the total number of grid points for each rectangle, including
# ghost nodes
# * i0: the index of the first node in each rectangle, minus 1
# * i0_real: the index of the first node in each rectangle, after removal
# of all ghost nodes, minus 1
df <- dplyr::mutate(
df,
nx = pmax(nodes_min, ceiling(1 + .data$Lx / grid_size)),
ny = pmax(nodes_min, ceiling(1 + .data$Ly / grid_size)),
hx = .data$Lx/(.data$nx - 1),
hy = .data$Ly/(.data$ny - 1),
ntotal = nodes_total(.data$nx, .data$ny, real_only = FALSE),
i0 = cumsum(c(0, utils::head(.data$ntotal, -1))),
i0_real = cumsum(c(0, utils::head(.data$nx, -1)*utils::head(.data$ny, -1)))
)
#positions of origins for each block
df$x0 <- x0
df$y0 <- y0
if (nrow(df) > 1) {
for (i in seq(nrow(df) - 1)) {
#find rows of connecting segments
ic1 <- which((df$type1 == "c") & (df$value1 == i))
ic1 <- ic1[ic1 > i]
ic2 <- which((df$type2 == "c") & (df$value2 == i))
ic2 <- ic2[ic2 > i]
ic3 <- which((df$type3 == "c") & (df$value3 == i))
ic3 <- ic3[ic3 > i]
ic4 <- which((df$type4 == "c") & (df$value4 == i))
ic4 <- ic4[ic4 > i]
#adjust position
df$x0[ic1] <- df$x0[i] + df$Lx[i]
df$y0[ic1] <- df$y0[i]
df$x0[ic2] <- df$x0[i]
df$y0[ic2] <- df$y0[i] - df$Ly[ic2]
df$x0[ic3] <- df$x0[i] - df$Lx[ic3]
df$y0[ic3] <- df$y0[i]
df$x0[ic4] <- df$x0[i]
df$y0[ic4] <- df$y0[i] + df$Ly[i]
}
}
#return
df
}
#' get all sparse entries and lhs for boundary conditions
#'
#' @description
#' function to get sparse matrix element positions for flow net finite
#' difference boundary conditions (known head, known flow or connected to
#' another domain)
#'
#' @param df tibble describing flow problem. Generated by function
#' `generate_flownet_properties()`
#' @importFrom magrittr `%>%`
#' @importFrom rlang .data
#' @return a list with two fields: `mat` containing a tibble with sparse
#' row (`row`), column (`col`) and values (`val`) of finite difference
#' matrix elements related to boundary conditions; and `lhs` which is a
#' vector with all left-hand side of equation values
#' @export
findiff_sparse_entries_bc <- function(df) {
#get long form for all boundary conditions
dbc <- tibble::tibble(
type = c(df$type1, df$type2, df$type3, df$type4),
value = c(df$value1, df$value2, df$value3, df$value4),
edge = rep(seq(4), each = nrow(df)),
id = rep(seq(nrow(df)), 4)
) %>%
dplyr::left_join(
df %>%
dplyr::select(dplyr::all_of(c("kx", "ky", "nx", "ny", "hx", "hy", "Lx", "Ly", "x0", "y0", "i0", "i0_real"))) %>%
dplyr::mutate(id = seq(nrow(df))),
by = "id"
) %>%
dplyr::mutate(
k = ifelse((.data$edge %in% c(1, 3)), .data$kx, .data$ky),
h = ifelse((.data$edge %in% c(1, 3)), .data$hx, .data$hy),
)
#initiate list and left-hand sides of bx's
mlist <- vector(mode = "list", length = nrow(dbc))
lhs <- rep(0, sum(df$ntotal))
#initiate list of connecting nodes
clist <- vector(mode = "list", length = nrow(dbc))
#loop through all bcs
for (i in 1:nrow(dbc)) {
if (dbc$type[i] == "h") {
#Defined head
i1 <- index_edge(dbc$nx[i], dbc$ny[i], dbc$edge[i], i0 = dbc$i0[i], offset = 0)
i2 <- index_edge(dbc$nx[i], dbc$ny[i], dbc$edge[i], i0 = dbc$i0[i], offset = 1)
mlist[[i]] <- tibble::tibble(
row = i1,
col = i2,
val = 1
)
lhs[i1] <- dbc$value[i]
} else if (dbc$type[i] == "q") {
#Defined flow rate (positive into domain)
i1 <- index_edge(dbc$nx[i], dbc$ny[i], dbc$edge[i], i0 = dbc$i0[i], offset = 0)
i2 <- index_edge(dbc$nx[i], dbc$ny[i], dbc$edge[i], i0 = dbc$i0[i], offset = 2)
mlist[[i]] <- tibble::tibble(
row = rep(i1, 2),
col = c(i1, i2),
val = rep(c(0.5, -0.5)*dbc$k[i]/dbc$h[i], each = length(i1))
)
lhs[i1] <- dbc$value[i]
} else if (dbc$type[i] == "c") {
#connected to other domain (b)
#index in <dbc> of connecting edge
ib <- which((dbc$id == dbc$value[i]) & (dbc$edge == (1 + (dbc$edge[i] + 1)%%4)))
if (dbc$edge[i] %in% c(2, 3)) {
#other domain connected on positive side - same head
ia1 <- index_edge(dbc$nx[i], dbc$ny[i], dbc$edge[i], i0 = dbc$i0[i], offset = 0)
ia2 <- index_edge(dbc$nx[i], dbc$ny[i], dbc$edge[i], i0 = dbc$i0[i], offset = 1)
ib2 <- index_edge(dbc$nx[ib], dbc$ny[ib], dbc$edge[ib], i0 = dbc$i0[ib], offset = 1)
mlist[[i]] <- tibble::tibble(
row = rep(ia1, 2),
col = c(ia2, ib2),
val = rep(c(-1, 1), each = length(ia1))
)
#add connecting real nodes - real node indexing - used later for solving flow potential
clist[[i]] <- tibble::tibble(
i1 = index_edge(dbc$nx[i], dbc$ny[i], dbc$edge[i], i0 = dbc$i0_real[i], offset = 0, real_only = TRUE),
i2 = index_edge(dbc$nx[ib], dbc$ny[ib], dbc$edge[ib], i0 = dbc$i0_real[ib], offset = 0, real_only = TRUE)
)
} else {
#other domain on negative side - same flow rate
ia1 <- index_edge(dbc$nx[i], dbc$ny[i], dbc$edge[i], i0 = dbc$i0[i], offset = 0)
ia2 <- index_edge(dbc$nx[i], dbc$ny[i], dbc$edge[i], i0 = dbc$i0[i], offset = 2)
ib1 <- index_edge(dbc$nx[ib], dbc$ny[ib], dbc$edge[ib], i0 = dbc$i0[ib], offset = 0)
ib2 <- index_edge(dbc$nx[ib], dbc$ny[ib], dbc$edge[ib], i0 = dbc$i0[ib], offset = 2)
mlist[[i]] <- tibble::tibble(
row = rep(ia1, 4),
col = c(ia1, ia2, ib1, ib2),
val = c(
rep(c(-0.5, 0.5)*dbc$k[i]/dbc$h[i], each = length(ia1)),
rep(c(-0.5, 0.5)*dbc$k[ib]/dbc$h[ib], each = length(ib1))
)
)
}
}
}
#return
return(list(
mat = dplyr::bind_rows(mlist),
lhs = lhs,
conn = dplyr::bind_rows(clist)
))
}
#' Solve flow net head problem for concatenated rectangular domains
#'
#' @description
#' Solves a flow net problem for a problem consisting of connected rectangular
#' grids. This function solves the hydraulic head using boundary conditions and
#' a finite difference approach, and also calculates the flow potential psi
#'
#' @param df tibble with problem description, generated by function
#' `generate_flownet_properties()`
#' @return a tibble with heads (field `h`), flow rates in x and y-direction
#' (`qx`, `qy`) and flow potential (`psi`) for each real point in the finite
#' difference grid. Each point is characterised by a position (`x`, `y`)
#' and an index `id` indicating which domain the point belongs to
#' @importFrom magrittr `%>%`
#' @importFrom rlang .data
#' @examples
#' df <- generate_flownet_properties()
#' dp <- solve_flownet(df)
#' @export
solve_flownet <- function(df) {
## SOLVE FOR HEAD
#get sparse matrix elements and left-hand side for boundary conditions
Mbc <- findiff_sparse_entries_bc(df)
#get sparse matrix elements for Poissons equation for each real node
M2x <- df %>%
dplyr::select(.data$nx, .data$ny, .data$kx, .data$hx, .data$i0) %>%
purrr::pmap_dfr(
function(nx, ny, kx, hx, i0) findiff_sparse_entries(nx, ny, order = 2, h = hx, direction = "x", multiplier = kx, i0_row = i0, i0_col = i0)
)
M2y <- df %>%
dplyr::select(.data$nx, .data$ny, .data$ky, .data$hy, .data$i0) %>%
purrr::pmap_dfr(
function(nx, ny, ky, hy, i0) findiff_sparse_entries(nx, ny, order = 2, h = hy, direction = "y", multiplier = ky, i0_row = i0, i0_col = i0)
)
#create sparse matrix
mat <- Matrix::sparseMatrix(
i = c(Mbc$mat$row, M2x$row, M2y$row),
j = c(Mbc$mat$col, M2x$col, M2y$col),
x = c(Mbc$mat$val, M2x$val, M2y$val),
dims = rep(sum(df$ntotal), 2)
)
#bind together, and solve system
h = Matrix::solve(mat, Mbc$lhs)
## CALCULATE FLOW FROM HEAD
#flow in x-direction
M1qx <- df %>%
dplyr::select(.data$nx, .data$ny, .data$kx, .data$hx, .data$i0) %>%
purrr::pmap_dfr(
function(nx, ny, kx, hx, i0) findiff_sparse_entries(nx, ny, order = 1, h = hx, direction = "x", multiplier = -kx, i0_row = i0, i0_col = i0)
)
qx <- as.vector(
Matrix::sparseMatrix(
i = M1qx$row,
j = M1qx$col,
x = M1qx$val,
dims = rep(sum(df$ntotal), 2)
) %*% h
)
#flow in y-direction
M1qy <- df %>%
dplyr::select(.data$nx, .data$ny, .data$ky, .data$hy, .data$i0) %>%
purrr::pmap_dfr(
function(nx, ny, ky, hy, i0) findiff_sparse_entries(nx, ny, order = 1, h = hy, direction = "y", multiplier = -ky, i0_row = i0, i0_col = i0)
)
qy <- as.vector(
Matrix::sparseMatrix(
i = M1qy$row,
j = M1qy$col,
x = M1qy$val,
dims = rep(sum(df$ntotal), 2)
) %*% h
)
## ASSIGN SOLUTIONS AND POSITIONS (real nodes only)
#indices of real nodes
i_real <- purrr::pmap(df, index_real) %>% unlist()
#generate solution object for all nodes
dp <- purrr::pmap_dfr(df, nodal_coordinates_real) %>%
dplyr::mutate(
h = h[i_real],
qx = qx[i_real],
qy = qy[i_real]
)
## GET FLOW POTENTIAL FROM FLOW
#x-direction elements - first order differentiation - real nodes only
M1x_el <- df %>%
dplyr::select(.data$nx, .data$ny, .data$kx, .data$hx, .data$i0_real) %>%
purrr::pmap_dfr(
function(nx, ny, kx, hx, i0_real) findiff_sparse_entries(nx, ny, order = 1, h = hx, direction = "x", i0_row = i0_real, i0_col = i0_real, real_only = TRUE)
)
#x-direction elements - first order differentiation - real nodes only
M1y_el <- df %>%
dplyr::select(.data$nx, .data$ny, .data$ky, .data$hy, .data$i0_real) %>%
purrr::pmap_dfr(
function(nx, ny, ky, hy, i0_real) findiff_sparse_entries(nx, ny, order = 1, h = hy, direction = "y", i0_row = i0_real, i0_col = i0_real, real_only = TRUE)
)
#flow potential must match on each of the connecting segments
Mc <- Mbc$conn
Mc_el <- tibble::tibble(
row = rep(seq(nrow(Mc)), 2),
col = c(Mc$i1, Mc$i2),
val = rep(c(-1, 1), each = nrow(Mc))
)
#notal number of real nodes
ntreal <- sum(df$nx*df$ny)
#crate single matrix and left-hand side for flow potential solving
mat_fp <- Matrix::sparseMatrix(
i = c(M1x_el$row, M1y_el$row + ntreal, Mc_el$row + 2*ntreal),
j = c(M1x_el$col, M1y_el$col, Mc_el$col),
x = c(M1x_el$val, M1y_el$val, Mc_el$val),
dims = c(2*ntreal + nrow(Mc), ntreal)
)
lhs_fp <- c(dp$qy, -dp$qx, rep(0, nrow(Mc)))
#solve for flow potential
psi <- as.vector(
Matrix::solve(
(Matrix::t(mat_fp) %*% mat_fp),
(Matrix::t(mat_fp) %*% lhs_fp)
)
)
#scale so flow potential ranges between 0 and Q_total, and assign
dp$psi <- psi - min(psi)
## RETURN
#return
return(dp)
}
#' Decide on number of equipotential intervals
#'
#' @description
#' This function takes a finite difference solution for both head `h` and flow
#' potential function `psi` to get the best number of equipotential intervals
#' `Nd` based on a given number of flow channels `Nf`. This function assumes
#' the permeabilities in all domains is the same
#'
#' @param df tibble with information about domain
#' @param dp tibble with head and flow potential results for each node
#' @param Nf number of requested flow channels
#' @examples
#' df <- generate_flownet_properties()
#' dp <- solve_flownet(df)
#' amount_equipotentialintervals(df, dp, Nf = 5)
#' @export
amount_equipotentialintervals <- function(df, dp, Nf = 5) {
#total head difference
dh <- max(dp$h) - min(dp$h)
#total flow per second
Q <- max(dp$psi) - min(dp$psi)
#k_avg
k_avg <- mean(sqrt(df$kx*df$ky))
#Nd
Nd <- round(k_avg*dh*Nf/Q)
#return
return(Nd)
}
#' Plot flow net in part of soil that is saturated
#'
#' @param df tibble with domains/problem description
#' @param dp flow net solution tibble. If not specified, it is calculated
#' using `df`
#' @param Nf number of requested flow paths
#' @param xlab,ylab x and y-axis label
#' @param fill_soilwet fill colour for saturated soil
#' @param fill_soildry fill colour for dry soil,
#' @param colour_soil line colour for soil
#' @param colour_flowline colour of flow lines
#' @param colour_equipotentialline colour of equipotential lines
#' @param fill_water fill colour for water
#' @param colour_water line colour for water
#' @param line_water line type for water edges (defined head)
#' @param line_width line thickness for all lines
#' @param xlim,ylim user-defined axis limits
#' @param axes if `FALSE` no axes are plotted
#' @return a ggplot object
#' @importFrom magrittr `%>%`
#' @importFrom rlang .data
#' @examples
#' df <- generate_flownet_properties(grid_size = 0.25)
#' ggplot_flownet_only(df, axes = FALSE)
#' ggplot_flownet_only(df, axes = TRUE)
#' @export
ggplot_flownet_only <- function(
df,
dp = NULL,
Nf = 5,
xlab = "x [m]",
ylab = "z [m]",
fill_soilwet = "#aebab7",
fill_soildry = "#d3bc5f",
colour_soil = "#65571d",
colour_flowline = "black",
colour_equipotentialline = "black",
fill_water = "#2a7fff",
colour_water = "#000080",
line_water = 2,
line_width = 0.5,
xlim = c(NA, NA),
ylim = c(NA, NA),
axes = TRUE
){
#if dp not provided, calculate heads and flow potential
if (is.null(dp)) {
dp <- solve_flownet(df)
}
#calculate number of equipotential intervals
Nd <- amount_equipotentialintervals(df, dp, Nf = Nf)
Nd_levels <- seq(min(dp$h), max(dp$h), l = Nd + 1)[2:Nd]
Nf_levels <- seq(min(dp$psi), max(dp$psi), l = Nf + 1)
#get head edges
dedge <- tibble::tibble(
type = c(df$type1, df$type2, df$type3, df$type4),
value = c(df$value1, df$value2, df$value3, df$value4),
id = rep(seq(nrow(df)), 4),
edge = rep(seq(4), each = nrow(df)),
x0 = rep(df$x0, 4),
y0 = rep(df$y0, 4),
Lx = rep(df$Lx, 4),
Ly = rep(df$Ly, 4)
) %>%
dplyr::filter(.data$type == "h") %>%
dplyr::mutate(
x = ifelse(.data$edge == 3, .data$x0 + .data$Lx, .data$x0),
y = ifelse(.data$edge == 2, .data$y0 + .data$Ly, .data$y0),
xend = ifelse(.data$edge %in% c(1, 3), .data$x, .data$x + .data$Lx),
yend = ifelse(.data$edge %in% c(1, 3), .data$y + .data$Ly, .data$y)
)
#plot rectangular soil elements
plt <- ggplot2::ggplot() +
ggplot2::annotate(
"rect",
xmin = df$x0,
ymin = df$y0,
xmax = df$x0 + df$Lx,
ymax = df$y0 + df$Ly,
fill = fill_soilwet,
color = NA
) +
ggplot2::coord_equal(ratio = 1, xlim = xlim, ylim = ylim, expand = FALSE)
#switch axes off if requested
if (axes == FALSE) {
plt <- plt + ggplot2::theme_void()
} else {
plt <- plt +
theme_soilmech() +
ggplot2::theme(
panel.background = ggplot2::element_blank(),
panel.border = ggplot2::element_blank()
) +
ggplot2::xlab(xlab) +
ggplot2::ylab(ylab)
}
#add flow and equipotential lines using contour lines
plt <- plt +
ggplot2::geom_contour(
data = dp,
ggplot2::aes(x = .data$x, y = .data$y, z = .data$h),
breaks = Nd_levels,
color = colour_equipotentialline,
size = line_width
) +
ggplot2::geom_contour(
data = dp,
ggplot2::aes(x = .data$x, y = .data$y, z = .data$psi),
breaks = Nf_levels,
color = colour_flowline,
size = line_width
)
#add water tables
plt <- plt +
ggplot2::annotate(
"segment",
x = dedge$x,
xend = dedge$xend,
y = dedge$y,
yend = dedge$yend,
linetype = line_water,
color = colour_water,
size = line_width
)
#return
return(plt)
}
#' Plot flow net and soil geometry for a dam/sheet pile problem
#'
#' @param df tibble with domains/problem description, generated by function
#' `generate_flownet_properties()`.#'
#' @param dp solution for heads and flow potential for each node. If not
#' defined, it is calculated in this plotting function
#' @param Nf number of requested flow paths
#' @param xlab,ylab x and y-axis label
#' @param fill_soilwet fill colour for saturated soil
#' @param fill_soildry fill colour for dry soil,
#' @param colour_soil line colour for soil
#' @param colour_flowline colour of flow lines
#' @param colour_equipotentialline colour of equipotential lines
#' @param fill_water fill colour for water
#' @param colour_water line colour for water
#' @param fill_impermeable fill colour for impermeable parts of the domain
#' @param fill_air fill colour for air on top of soil/water
#' @param line_water line type for water edges (defined head)
#' @param line_width line thickness for all lines
#' @param xlim,ylim user-defined axis limits
#' @param axes if `FALSE` no axes are plotted
#' @param thickness_impermeable thickness of impermeable border around the
#' entire problem. Defined as an array for all edges (left, top, right,
#' bottom), or single value for all edges
#' @param scale_soilmarker size of soil marker, in fraction of height
#' @param scale_watermarker size of water table marker, in fraction of height
#' @return a ggplot object
#' @importFrom magrittr `%>%`
#' @examples
#' #default example
#' df <- generate_flownet_properties()
#' ggplot_flownet_dam(df)
#'
#' #anisotropic permeability - 6 flow channels
#' df <- generate_flownet_properties(kx = 4e-6, ky = 1e-6)
#' ggplot_flownet_dam(df, Nf = 6)
#'
#' #excavation next to river
#' df <- generate_flownet_properties(
#' Lx = c(20, 20, 0.2, 5, 5),
#' Ly = c(10, 5, 5, 5, 2),
#' Ly_dry = c(2, 0, 0, 0, 0),
#' type1 = c("q", "q", "c", "c", "q"),
#' type2 = c("h", "c", "q", "c", "h"),
#' type3 = c("q", "c", "c", "q", "q"),
#' type4 = c("c", "q", "q", "q", "c"),
#' value1 = c(0, 0, 2, 3, 0),
#' value2 = c(12, 1, 0, 5, 6),
#' value3 = c(0, 3, 4, 0, 0),
#' value4 = c(2, 0, 0, 0, 4)
#' )
#' ggplot_flownet_dam(df)
#' @export
ggplot_flownet_dam <- function(
df,
dp = NULL,
Nf = 5,
xlab = "x [m]",
ylab = "z [m]",
fill_soilwet = "#aebab7",
fill_soildry = "#d3bc5f",
colour_soil = "#65571d",
colour_flowline = "black",
colour_equipotentialline = "black",
fill_water = "#2a7fff",
colour_water = "#000080",
fill_impermeable = "#333333",
fill_air = "white",
line_water = 2,
line_width = 0.5,
xlim = c(NA, NA),
ylim = c(NA, NA),
axes = TRUE,
thickness_impermeable = c(0, 1, 0, 1),
scale_soilmarker = 0.075,
scale_watermarker = 0.050
){
#if dp not provided, calculate heads and flow potential
if (is.null(dp)) {
dp <- solve_flownet(df)
}
#calculate number of equipotential intervals
Nd <- amount_equipotentialintervals(df, dp, Nf = Nf)
Nd_levels <- seq(min(dp$h), max(dp$h), l = Nd + 1)[2:Nd]
Nf_levels <- seq(min(dp$psi), max(dp$psi), l = Nf + 1)
#get head edges
dedge <- tibble::tibble(
type = c(df$type1, df$type2, df$type3, df$type4),
value = c(df$value1, df$value2, df$value3, df$value4),
id = rep(seq(nrow(df)), 4),
edge = rep(seq(4), each = nrow(df)),
x0 = rep(df$x0, 4),
y0 = rep(df$y0, 4),
Lx = rep(df$Lx, 4),
Ly = rep(df$Ly, 4)
)
if ("Ly_dry" %in% colnames(df)) {
dedge$Ly_dry <- rep(df$Ly_dry, 4)
} else {
dedge$Ly_dry <- dedge$Ly
}
dedge <- dedge %>%
dplyr::filter(.data$type == "h") %>%
dplyr::mutate(
x = ifelse(.data$edge == 3, .data$x0 + .data$Lx, .data$x0),
y = ifelse(.data$edge == 2, .data$y0 + .data$Ly, .data$y0),
xend = ifelse(.data$edge %in% c(1, 3), .data$x, .data$x + .data$Lx),
yend = ifelse(.data$edge %in% c(1, 3), .data$y + .data$Ly, .data$y)
)
#impermeable thickness
if (length(thickness_impermeable) == 1) {
thickness_impermeable <- rep(thickness_impermeable, 4)
}
#plot limits
if (is.na(xlim[1])) {
xlim[1] <- min(df$x0) - thickness_impermeable[1]
}
if (is.na(xlim[2])) {
xlim[2] <- max(df$x0 + df$Lx) + thickness_impermeable[3]
}
if (is.na(ylim[1])) {
ylim[1] <- min(df$y0) - thickness_impermeable[4]
}
if (is.na(ylim[2])) {
ylim[2] <- max(c(df$y0 + df$Ly + df$Ly_dry, dedge$value)) + thickness_impermeable[2]
}
#plot impermeable polygon
dimpe <- tibble::tibble(
x = c(rep(xlim[1], 2), rep(xlim[2], 2)),
y = c(ylim[1], rep(ylim[2], 2), ylim[1])
)
#initiate plot
plt <- ggplot2::ggplot() +
ggplot2::coord_equal(ratio = 1, xlim = xlim, ylim = ylim, expand = FALSE)
#impermeable background
plt <- plt +
ggplot2::geom_polygon(
data = dimpe,
ggplot2::aes(x = .data$x, y = .data$y),
color = NA,
fill = fill_impermeable
)
#plot rectangular saturated soil elements
plt <- plt + ggplot2::annotate(
"rect",
xmin = df$x0,
ymin = df$y0,
xmax = df$x0 + df$Lx,
ymax = df$y0 + df$Ly,
fill = fill_soilwet,
color = NA
)
#plot rectangles for dry soil
ddry <- dplyr::filter(dedge, .data$Ly_dry > 0)
plt <- plt +
ggplot2::geom_rect(
data = ddry,
ggplot2::aes(xmin = .data$x, xmax = .data$xend, ymin = .data$y, ymax = .data$y + .data$Ly_dry),
fill = fill_soildry,
colour = NA
)
#plot whitespace above dry soil water
plt <- plt +
ggplot2::geom_rect(
data = dedge,
ggplot2::aes(xmin = .data$x, xmax = .data$xend, ymin = .data$y + .data$Ly_dry, ymax = ylim[2]),
fill = fill_air,
colour = NA
)
#plot rectangles for ponding water
dwat <- dplyr::filter(dedge, .data$value > .data$y + .data$Ly_dry)
plt <- plt +
ggplot2::geom_rect(
data = dwat,
ggplot2::aes(xmin = .data$x, xmax = .data$xend, ymin = .data$y + .data$Ly_dry, ymax = .data$value),
fill = fill_water,
color = NA
)
#plot soil surfaces
plt <- plt +
ggplot2::annotate(
"segment",
x = dedge$x,
xend = dedge$xend,
y = dedge$y + dedge$Ly_dry,
yend = dedge$y + dedge$Ly_dry,
color = colour_soil,
linetype = 1,
size = line_width
)
#plot soil surface marker and water table marker
for (i in 1:nrow(dedge)) {
if ((dedge$xend[i] > xlim[1]) & (dedge$x[i] <= xlim[2])) {
if (!is.na(scale_soilmarker)) {
plt <- ggplot_add_soilmarker(
plt,
1/3*max(xlim[1], dedge$x[i]) + 2/3*min(xlim[2], dedge$xend[i]),
dedge$y[i] + dedge$Ly_dry[i],
scale = scale_soilmarker*diff(ylim)
)
}
if (!is.na(scale_watermarker)) {
plt <- ggplot_add_watermarker(
plt,
2/3*max(xlim[1], dedge$x[i]) + 1/3*min(xlim[2], dedge$xend[i]),
dedge$value[i],
scale = scale_watermarker*diff(ylim)
)
}
}
}
#switch axes off if requested
if (axes == FALSE) {
plt <- plt + ggplot2::theme_void()
} else {
plt <- plt +
theme_soilmech() +
ggplot2::theme(
panel.background = ggplot2::element_blank(),
panel.border = ggplot2::element_blank()
) +
ggplot2::xlab(xlab) +
ggplot2::ylab(ylab)
}
#add flow and equipotential lines using contour lines
plt <- plt +
ggplot2::geom_contour(
data = dp,
ggplot2::aes(x = .data$x, y = .data$y, z = .data$h),
breaks = Nd_levels,
color = colour_equipotentialline,
size = line_width
) +
ggplot2::geom_contour(
data = dp,
ggplot2::aes(x = .data$x, y = .data$y, z = .data$psi),
breaks = Nf_levels,
color = colour_flowline,
size = line_width
)
#add water tables
plt <- plt +
ggplot2::annotate(
"segment",
x = dedge$x,
xend = dedge$xend,
y = dedge$y,
yend = dedge$yend,
linetype = line_water,
color = colour_water,
size = line_width
)
#return
return(plt)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.