Purpose

The purpose of this project is to develop a method to streamline the production of longitudinal profile graphs for calibration runs of HEC-RAS hydrologic models. This project will focus on the Upper Mississippi River Flood Risk Management (UMR-FRM) Hydrologic Model. HEC-RAS supports the production of longitudinal profile graphs, but does not provide sufficient flexibility for producing these graphs for long river reaches. Therefore, exporting the model results to another application which permits more automation and more flexible visualization options is required. R has been selected for its flexibility and visualization capabilities.

Longitudinal profile graph required elements

The following graph elements are required to be displayed on the longitudinal profile graphs:

Export HEC-RAS model results

The HEC-RAS Users Manual, Chapter 9 Viewing Results describes how to display tabular data (section 9-13). Tabular data displayed using the Detailed Output Tables, Profile Summary Tables, and User Defined Tables can be transferred to Excel using the "Windows Clipboard" method. Use the following steps to perform the export:

Alternatively, data can be exported from HEC-RAS to HEC-DSS format (section 9-34). The DSS-R interface project R package provides functions to read and write data from a HEC-DSS file by making calls to the Java methods exposed in HEC-DSSVue's scripting capability. This method has been tested, but not pursued for this project.

Import HEC-RAS model results into R

Import HEC-RAS model data into R.

library(tidyverse)
library(readr)

# Set the working directory
#setwd( system.getCurrentDirectory() )  

# Import csv file of RAS results
files <- list.files(path = "Data/SummaryTable", pattern = "*.csv", full.names = T)

hydroModel <- sapply(files, read_csv, simplify=FALSE) %>% 
bind_rows(.id = "id")

hydroModel <- select(hydroModel, -c(id))

#hydroModel <- read_csv(file = "Data/SummaryTable/Results.csv")

Import levee elevation data

Import levee elevation data into R.

library(dplyr)

levees <- read_csv(file = "Data/Levees/Levees_Authorized_Existing.csv")

# Create factors
levees$descending_bank <- factor(levees$descending_bank)
levees$levee <- factor(levees$levee)
# Sort data frame
levees <- arrange(levees, descending_bank, levee, elevation_type, river_mile)

levees <- drop_na(levees)

Import high water marks

Import high water mark data into R.

library(lubridate)

high_water <- read_csv(file = "Data/HighWaterMarks/UMR_high_water_marks.csv")

#high_water$peak_date <- as.Date(high_water$peak_date, format = "%m/%d/%y")
high_water$peak_date <- mdy(high_water$peak_date)
high_water$event <- as.character(year(high_water$peak_date))
high_water <- filter(high_water, event == "2017" | event == "2014" | event == "2013" | event == "2008")

Import bridge elevations

Import a data frame of bridge min/max elevations.

bridges <- read_csv(file = "Data/BridgeHeights/Bridge_elevations.csv")
bridges

Import gage locations

Import a data frame of gage locations.

gages <- read_csv(file = "Data/GageLocations/gage_locations.csv")

# readr::read_csv mangles newlines in strings. Given R's inscrutable handling of backslashes in strings, replace "\\n" with "\n".

gages$name <- gsub("\\n", "\n", gages$name, fixed=TRUE)
gages

Import river features - Need to have "dummy" river feature at end of last plot to keep x-axis consistent through all the plot pages, otherwise final plot page will be shortened in the x direction. Calculate total river miles and how many miles per plot, then determine the remainder of river miles on the last plot. Put "dummy" feature at minimum river mile to keep x-axis consistent.

Import a data frame of river features.

features <- read_csv(file = "Data/RiverFeatures/River_features.csv") 

# readr::read_csv mangles newlines in strings. Given R's inscrutable handling of backslashes in strings, replace "\\n" with "\n".

features$name <- gsub("\\n", "\n", features$name, fixed=TRUE)
features

Create gage labels

Create a new data frame to store gage label information based on the gage data frame.

# Set the interval for gage stage labels. 
stage_interval_label <- 5
# Create the data frame to hold gage stage labels. 
gage_labels <- data.frame(name = character(), river_mile = double(), elevation = double(), stage = double())
# Iterate through gages to create gage stage labels. 
for (i in gages$name) {
    name       <- gages[gages$name == i,]$name
    river_mile <- gages[gages$name == i,]$river_mile
    # Identify the min and max stage label, rounded to the nearest stage_gage_label. 
    min_stage  <- ceiling(gages[gages$name == i,]$min_stage / stage_interval_label) * stage_interval_label
    max_stage  <- floor(gages[gages$name == i,]$max_stage / stage_interval_label) * stage_interval_label
    # Iterate through the min and max stages for each gage creating the labels. 
    for (j in seq(min_stage, max_stage, by = stage_interval_label)) {
        elevation <- gages[gages$name == i,]$elevation + j
        stage     <- j
        gage_labels <- rbind(gage_labels, data.frame(name = name, river_mile = river_mile, elevation = elevation, stage = stage))
    }
}
#gage_labels

Create gage boxes

Use the gage data frame to create boxes that can be used to draw gages on plots.

# Set the interval for gage stage boxes. 
stage_interval_boxes <- 1
# Set the width of the stage boxes (units: river miles)
box_width <- 0.1
# Create the data frame to hold gage stage boxes. 
gage_boxes <- data.frame(gage_stage = character(), river_mile = double(), x = double(), y = double())
# Iterate through gages to create gage stage boxes. 
for (i in gages$name) {
    name       <- gages[gages$name == i,]$name
    river_mile <- gages[gages$name == i,]$river_mile
    min_stage  <- gages[gages$name == i,]$min_stage
    max_stage  <- gages[gages$name == i,]$max_stage
    # Iterate through the min and max stages for each gage creating the boxes. 
    for (j in seq(min_stage, max_stage, by = stage_interval_boxes)) {
        elevation <- gages[gages$name == i,]$elevation + j
        stage     <- j
        gage_stage <- paste(name, stage)
        # Calculate the box coordinates for the curent stage. Box coordinate 1 starts in the lower left corner. 
        gage_box_1 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile + (box_width / 2), y = elevation)
        gage_box_2 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile + (box_width / 2), y = elevation + 1)
        gage_box_3 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile - (box_width / 2), y = elevation + 1)
        gage_box_4 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile - (box_width / 2), y = elevation)
        gage_box_5 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile + (box_width / 2), y = elevation)
        # Append the box coordinates for the current stage to the main data frame, gage_boxes
        gage_boxes <- rbind(gage_boxes, gage_box_1, gage_box_2, gage_box_3, gage_box_4, gage_box_5)
    }
}
rm(stage_interval_boxes, box_width, gage_box_1, gage_box_2, gage_box_3, gage_box_4, gage_box_5)
#gage_boxes

Create flat pool elevations

Create a data frame to hold flat pool elevations.

pools <- tribble(
    ~name,                                   ~start_river_mile, ~end_river_mile, ~elevation,
    #----------------------------------------/------------------/----------------/----------
    "Dam No. 11 Pool",                        615.1,             583.0,           602.5,
    "Dam No. 12 Pool",                        583.0,             556.7,           591.5,
    "Dam No. 13 Pool",                        556.7,             522.5,           582.5,
    "Dam No. 14 Pool",                        522.5,             493.3,           571.5,
    "Dam No. 15 Pool",                        493.3,             482.8,           560.5,
    "Dam No. 16 Pool",                        482.8,             457.1,           544.5,
    "Dam No. 17 Pool",                        457.1,             437.1,           535.5,
    "Dam No. 18 Pool",                        437.1,             410.5,           518.06,
    "Dam No. 19 Pool",                        410.5,             364.3,           518.2,
    "Dam No. 20 Pool",                        364.3,             343.3,           480.0,
    "Dam No. 21 Pool",                        343.3,             324.9,           470.0,
    "Dam No. 22 Pool",                        324.9,             301.2,           459.5
)

Assign Plot Pages

The purpose of this step is to determine how many plot pages are needed and which river miles will appear on which plot pages.

# Set the interval for gage stage boxes. 
stage_interval_boxes <- 1
# Set the width of the stage boxes (units: river miles)
box_width <- 0.1
# Create the data frame to hold gage stage boxes. 
gage_boxes <- data.frame(gage_stage = character(), river_mile = double(), x = double(), y = double())
# Iterate through gages to create gage stage boxes. 
for (i in gages$name) {
    name       <- gages[gages$name == i,]$name
    river_mile <- gages[gages$name == i,]$river_mile
    min_stage  <- gages[gages$name == i,]$min_stage
    max_stage  <- gages[gages$name == i,]$max_stage
    # Iterate through the min and max stages for each gage creating the boxes. 
    for (j in seq(min_stage, max_stage, by = stage_interval_boxes)) {
        elevation <- gages[gages$name == i,]$elevation + j
        stage     <- j
        gage_stage <- paste(name, stage)
        # Calculate the box coordinates for the curent stage. Box coordinate 1 starts in the lower left corner. 
        gage_box_1 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile + (box_width / 2), y = elevation)
        gage_box_2 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile + (box_width / 2), y = elevation + 1)
        gage_box_3 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile - (box_width / 2), y = elevation + 1)
        gage_box_4 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile - (box_width / 2), y = elevation)
        gage_box_5 <- data.frame(gage_stage = gage_stage, river_mile = river_mile, x = river_mile + (box_width / 2), y = elevation)
        # Append the box coordinates for the current stage to the main data frame, gage_boxes
        gage_boxes <- rbind(gage_boxes, gage_box_1, gage_box_2, gage_box_3, gage_box_4, gage_box_5)
    }
}
rm(stage_interval_boxes, box_width, gage_box_1, gage_box_2, gage_box_3, gage_box_4, gage_box_5)
#gage_boxes

Assign Plot Pages

The purpose of this step is to determine how many plot pages are needed and which river miles will appear on which plot pages.

# Max function that returns zero if vector contains all NAs
max0 <- function (x) ifelse( !all(is.na(x)), max(x, na.rm = TRUE), 0 )
# Min function that returns zero if vector contains all NAs
min0 <- function (x) ifelse( !all(is.na(x)), min(x, na.rm = TRUE), 0 )
# Specify number of river miles per plot
miles_per_plot <- 30
# Determine the number of plots needed
num_plots <- ceiling( (max(hydroModel$River_Sta) - min(hydroModel$River_Sta)) / miles_per_plot )
# Create an empty data frame of plots with fields for starting and ending river miles, min and max y-axis values. 
plot_pages <- tibble(plot       = 1:num_plots, 
                     start_mile = rep(NA, num_plots), 
                     end_mile   = rep(NA, num_plots),
                     max_y      = rep(0, num_plots),
                     mid_y      = rep(0, num_plots),
                     min_y      = rep(0, num_plots),
                     y_range    = rep(0, num_plots),
                     plot_max_y = rep(0, num_plots),
                     plot_min_y = rep(0, num_plots),
                     plot_dif_y = rep(0, num_plots))
# Initialize beginning and ending river miles of the first plot
plot_pages[1,]$start_mile <- max(hydroModel$River_Sta)
plot_pages[1,]$end_mile   <- max(hydroModel$River_Sta) - miles_per_plot
# Iterate through the remaining plots specifiying their starting and ending river miles
for (j in 2:num_plots) {
    plot_pages[j,]$start_mile <- plot_pages[j-1,]$end_mile
    plot_pages[j,]$end_mile   <- plot_pages[j,]$start_mile - miles_per_plot
}
# Iterate through plot pages determining the min and max y-axis values for each data series
for (k in 1:num_plots) {
    # Set start and end mile for the current plot
    start_mile <- plot_pages[plot_pages$plot == k,]$start_mile
    end_mile   <- plot_pages[plot_pages$plot == k,]$end_mile
    # Subset relevant data frames for the current plot
    hm <-  hydroModel[hydroModel$River_Sta    <= start_mile & hydroModel$River_Sta     >= end_mile,]
    hw <-  high_water[high_water$river_mile   <= start_mile & high_water$river_mile    >= end_mile,]
    l <-   levees[levees$river_mile   <= start_mile & levees$river_mile    >= end_mile,]
    #g  <-       gages[gages$river_mile        <= start_mile & gages$river_mile         >= end_mile,]
    # Set plot_pages$max_y
    aa <- c( max0(hm$WS_Elev), max0(hw$elevation_NAVD88), max0(l$elevation_NAVD88) )
    plot_pages[plot_pages$plot == k,]$max_y <- max0(aa[aa > 0])
    # Set plot_pages$min_y
    bb <- c( min0(hm$WS_Elev), min0(hw$elevation_NAVD88), min0(l$elevation_NAVD88) )
    plot_pages[plot_pages$plot == k,]$min_y <- min0(bb[bb > 0])
    # Calculate y range
    plot_pages[plot_pages$plot == k,]$y_range <- plot_pages[plot_pages$plot == k,]$max_y - plot_pages[plot_pages$plot == k,]$min_y
    # Calculate mid y
    plot_pages[plot_pages$plot == k,]$mid_y <- plot_pages[plot_pages$plot == k,]$max_y - ( plot_pages[plot_pages$plot == k,]$y_range / 2)
}
# Determine the max y range for all of the plots
plot_y_range <- ceiling(max(plot_pages$y_range))
# Iterate through plots specifying their plot_max_y, plot_min_y, and plot_dif_y
for (l in 1:num_plots) {
    plot_pages[l,]$plot_max_y <- ceiling(plot_pages[l,]$mid_y + (plot_y_range / 2))
    plot_pages[l,]$plot_min_y <- floor(plot_pages[l,]$mid_y - (plot_y_range / 2))
    plot_pages[l,]$plot_dif_y <- plot_pages[l,]$plot_max_y - plot_pages[l,]$plot_min_y
}
# Display plot pages table
plot_pages

Profile Graph Function

library(ggplot2)
library(ggrepel)
library(scales)
library(dplyr)

# longitudinal_profile_plot function
longitudinal_profile_plot <- function(plot_number) {
    # Get values from the plot_pages data frame for the current plot
    start_mile <- plot_pages[plot_pages$plot == plot_number,]$start_mile
    end_mile   <- plot_pages[plot_pages$plot == plot_number,]$end_mile
    plot_max_y <- plot_pages[plot_pages$plot == plot_number,]$plot_max_y
    plot_min_y <- plot_pages[plot_pages$plot == plot_number,]$plot_min_y
    # Subset data frames for the current plot
    hm <-  hydroModel[hydroModel$River_Sta    <= start_mile & hydroModel$River_Sta     >= end_mile,]
    hw <-  high_water[high_water$river_mile   <= start_mile & high_water$river_mile    >= end_mile,]
    l  <-      levees[levees$river_mile       <= start_mile & levees$river_mile        >= end_mile,]
    g  <-       gages[gages$river_mile        <= start_mile & gages$river_mile         >= end_mile,]
    gl <- gage_labels[gage_labels$river_mile  <= start_mile & gage_labels$river_mile   >= end_mile,]
    gb <-  gage_boxes[gage_boxes$river_mile   <= start_mile & gage_boxes$river_mile    >= end_mile,]
    f  <-    features[features$river_mile     <= start_mile & features$river_mile      >= end_mile,]
    # b  <-     bridges[bridges$river_mile      <= start_mile &  bridges$river_mile       >= end_mile,]

    # Create levee labels for the current plot
    levee_labels <- summarize(group_by(l, levee, descending_bank), 
                              river_mile = mean(river_mile),
                              elevation_NAVD88 = mean(elevation_NAVD88)+2)

    hm$Freq <- as.factor(hm$Freq)

    # Define colors. Inspired by palettes from https://wesandersonpalettes.tumblr.com/ using names from colors(). 
    cols <- c("2 Year" = "coral1", 
              "3 Year" = "red", 
              "4 Year" = "orangered1", 
              "5 Year" = "orangered3", 
              "6 Year" = "darkorange",
              "7 Year" = "orange", 
              "8 Year" = "yellow",
              "9 Year" = "gold1",
              "10 Year" = "olivedrab2",
              "15 Year" = "green",
              "20 Year" = "chartreuse3",
              "33 Year" = "green4",
              "50 Year" = "seagreen3",
              "100 Year" = "skyblue",
              "200 Year" = "deepskyblue2",
              "500 Year" = "deepskyblue4",
              "1000 Year" = "blue",
              "2000 Year" = "navyblue",
              "5000 Year" = "orchid",
              "10000 Year" = "darkmagenta",
              "20000 Year" = "magenta",
              "100000 Year" = "deeppink")
    legend_limits <- c("2 Year", "3 Year", "4 Year", "5 Year", "6 Year", "7 Year", "8 Year", "9 Year", "10 Year", "15 Year", "20 Year", "33 Year", "50 Year", "100 Year", "200 Year", "500 Year", "1000 Year", "2000 Year", "5000 Year", "10000 Year", "20000 Year", "100000 Year")
    legend_labels <- c("2 Year", "3 Year", "4 Year", "5 Year", "6 Year", "7 Year", "8 Year", "9 Year", "10 Year", "15 Year", "20 Year", "33 Year", "50 Year", "100 Year", "200 Year", "500 Year", "1000 Year", "2000 Year", "5000 Year", "10000 Year", "20000 Year", "100000 Year")


    # Create the plot
    p <- ggplot(data = hm, 
                aes(x = River_Sta, y = WS_Elev, color = Freq)) +
      geom_line(size = 1) +

         scale_color_manual(values = cols, labels = legend_labels, limits = legend_limits) + 
         scale_shape_manual(values = c(1, 1, 1, 1, 3, 1, 1), labels = legend_labels) +
         theme_bw() + 
         coord_cartesian(ylim = c(plot_min_y, plot_max_y)) + 
         scale_x_reverse(breaks = seq(from = 620, to = 0, by = -10),
                         minor_breaks = seq(from = ceiling(start_mile), to = floor(end_mile), by = -1),
                         expand = c(0.01,0)) + 
         scale_y_continuous(breaks = seq(from = 320, to = 620, by = 10),
                            minor_breaks = seq(from = plot_min_y, to = plot_max_y, by = 1),
                            expand = c(0.01,0),
                            oob = squish) + 
         theme(legend.position = c(.99, .99), 
               legend.justification = c("right", "top"),
               legend.background = element_rect(fill = alpha('white', 0.6)),
               legend.title = element_blank(),
               panel.grid.major = element_line(colour = "grey10", size = 0.1)) +
         labs(title = "Upper Mississippi River Hydraulic Model - Flow Frequency Profiles",
              x = "Miles Above Ohio River", 
              y = "Elevation (NAVD88 feet)") + 

        #Adjust Font Size
        theme(plot.title=element_text(family="ArialMT", size=14), plot.subtitle = element_text(family="ArialMT", size=12)) +

         # Draw high water marks
       # geom_point(inherit.aes = FALSE,
        #            data = hw,
         #           aes(x = river_mile, y = elevation_NAVD88, color = event),
          #          show.legend = FALSE, size = 3, shape = 4, stroke = 1.25) +

        # geom_point(inherit.aes = FALSE,
        #             data = hw,
        #             shape = 19, 
        #             aes(x = river_mile, y = elevation_NAVD88, color = hw),
        #             show.legend = FALSE, size = 1) + 

        # Draw raw levee lines, existing elevation
       #  geom_line(inherit.aes = FALSE,
        #            data = filter(l, elevation_type == "2016_NLD"),
         #           aes(x = river_mile, y = elevation_NAVD88, group = levee, color = descending_bank),
          #          show.legend = FALSE, size = 0.4, alpha = 0.8) +
        #geom_line(inherit.aes = FALSE, 
         #          data = filter(l, elevation_type == "2016_NLD_auth"),
          #         aes(x = river_mile, y = elevation_NAVD88, group = levee, color = descending_bank),
           #        show.legend = FALSE, size = 0.4, alpha = 0.8) +
        #geom_line(inherit.aes = FALSE, 
         #          data = filter(l, elevation_type == "Terrain"),
          #         aes(x = river_mile, y = elevation_NAVD88, group = levee, color = descending_bank),
           #        show.legend = FALSE, size = 0.4, alpha = 0.8) +
        # geom_line(inherit.aes = FALSE, 
        #            data = filter(l, elevation_type == "Authorized"),
        #            aes(x = river_mile, y = elevation_NAVD88, group = levee, color = descending_bank),
        #           show.legend = FALSE, size = 0.4, alpha = 0.8) +
         # Draw smooth levee lines, existing elevation
         # geom_smooth(inherit.aes = FALSE, 
                        # data = filter(l, elevation_type == "2016_NLD"),
                        # aes(x = river_mile, y = elevation_NAVD88, group = levee, color = descending_bank),
                        # method = "gam", formula = y ~ s(x, bs = "cs"), se = FALSE, size = 0.4) + 

    # Add a line for the river feature 
         #geom_segment(data = f,
          #            aes(x = river_mile, y = 0, xend=-Inf, yend= 4),
           #           inherit.aes = FALSE) +    


        # Label levees
         #geom_label_repel(inherit.aes = FALSE, 
          #               data = levee_labels,
           #              aes(x = river_mile, y = elevation_NAVD88, label = levee, color = descending_bank),
            #             show.legend = FALSE, size = 3, force = 1, label.size = 0.2, segment.size = 0, 
             #            box.padding = unit(0.1, "lines"), fill = alpha("white", 0.6)) + 

        # Draw the gage boxes
         geom_path(inherit.aes = FALSE, 
                   data = gb, 
                   aes(x = x, y = y, group = gage_stage),
                   size = 0.1) + 

        # Label gage stages
         geom_text(inherit.aes = FALSE, 
                   data = gl, 
                   aes(x = river_mile, y = elevation, label = stage),
                   hjust = 0, nudge_x = 0.1, size = 2.5) +

         # Label gages
         geom_text_repel(inherit.aes = FALSE, 
                         data = g, 
                         aes(x = river_mile, y = (elevation + min_stage) + 2, label = name),
                         nudge_x = -0.4, angle = 90, size = 3, fontface = "bold", force = 0.1, segment.size = 0) + 

          # Label river features
         geom_text_repel(inherit.aes = FALSE, 
                         data = f, 
                         aes(x = river_mile, y = rep(plot_min_y - 0, length(name)), label = name),
                         nudge_x = 0, angle = 90, size = 2.5, force = 0.01, segment.size = 0) 

      # Draw bridge elevations
         # geom_errorbar(inherit.aes = FALSE, 
                    #   data = b,
                     #  aes(x = river_mile, ymin = lowest_elevation, ymax = highest_elevation),
                     #  width = 0.5, size = 0.2, color = "red4")


    return(p)
}
#    p
# Iterate through plot pages to produce longitudinal profile plots
for (i in plot_pages$plot) {

    # insert vertical white space so that the next figure is on a new page
    cat('\\pagebreak')

    # Set the current plot number
    plot_number <- plot_pages[plot_pages$plot == i,]$plot

    # Create the longitudinal profile plot
    print(longitudinal_profile_plot(plot_number))

    #Save Plots
    aspect_ratio <- 2.5
    height <- 7
    ggsave(sprintf("ProfilePlot.%s.png", i), height = 7 , width = 7 * aspect_ratio)
}

```



mpdougherty/razviz documentation built on April 1, 2021, 4:16 p.m.