Groundwater Contouring

R Markdown

The process of generating groundwater elevation contours assumes several things. The most important step in generating groundwater contours is developing a good conceptual understanding of the hydrogeology of a region under study. The key question is “Do all the well observations represent the same aquifer system or should they be considered seperate aquifer systems?” If you determine that the wells can be treated as a single aquifer system then a single groundwater contour map makes sense but if multiple aquifer systems are represented then it is most appropriate to generate a seperate groundwater contour map for each aquifer.

It is assumed that the user of this script has a strong conceptual understanding of hydrogeology.

This example assumes all wells are part of the same aquifer system.

Getting started with groundwater plots

The groundwater contouring script shown here includes many steps, functions, and several libraries.

First I will show the steps in creating groundwater contours using my data and at the end I will give example outputs from my data inputs.

Functions to make contour lines and spatial objects from well elevations

contour_function <- function(well_observations=wl_obs, year, season, crop_to_data = TRUE,datum="gs",map_output="return_raster"){
  library("sp")
  library("mapview")
  library("raster")
  library("fields")
  library("maptools")
  library("rgeos")
  library("rgdal")
  library("prettymapr")
  library("raster")
  library("rgeos")
  library(lubridate)

  # Subfunction 1: takes "season" input from outer function and converts to dates.
  # Handles "fall", "spring", and specific date windows. 
  season_date_maker = function(year = 2015, season = "fall"){
    if(season == "fall"){
      fall_start = "09 01"; fall_end = "11 30" #format: "mm dd"
      season_dates = c(ymd(paste(year, fall_start)), ymd(paste(year, fall_end)))
    } else if (season == "spring"){
      spring_start = "03 01"; spring_end = "05 31"
      season_dates = c(ymd(paste(year, spring_start)), ymd(paste(year, spring_end)))
    } else if(length(season) == 2 & is.Date(season)){season_dates = season
    } else {print("Please specify fall, spring, or a 2-element array of date-type objects")}
    return(season_dates)
  }
  
  gw_elevation_subsetting = function(year, season){
    
    
    # dem_watershed  <- raster(file.path(here::here(), "Reference_Spatial_Files_Temp" ,"merged_dem_siskiyou_10m_3310_brad.tif"))
    # dem_watershed  <- raster(file.path(here::here(), "Reference_Spatial_Files_Temp" ,"merged_dem_siskiyou_10m_3310_bill.tif"))
    
    #Build season start and end dates from year/season or pull from specified dates
    season_dates <- season_date_maker(year, season)
    
    #### Subset water level data - first pass
    gw_elevation.subset = subset(well_observations,
                                 well_observations$date >= season_dates[1] &
                                   well_observations$date <= season_dates[2] &
                                   gs_to_ws_ft != "NA")
    
    gw_elevation.subset <- merge(gw_elevation.subset, wells, by="well_code", no.dups=TRUE, all=FALSE)
    
    # 2. Make point data spatial and reproject all spatial data ------------------
    
    #### Make spatial point data
    gw_elevation.spatial <- gw_elevation.subset
    coordinates(gw_elevation.spatial) = ~longitude + latitude #Make the point-perforations table spatial
    proj4string(gw_elevation.spatial) <- CRS("+init=epsg:4326") #assign WGS84 projection to coordinates
    
    #Reproject wl points and basin boundary
    gw_elevation.spatial <- spTransform(gw_elevation.spatial, crs(wsh))
    
    # Check if the point is over the watershed
    gw_elevation.spatial <- gw_elevation.spatial[wsh,]
    
    gw_elevation.spatial$gp_elev_ft <- extract(dem_watershed, gw_elevation.spatial, method='bilinear')*3.28084
    
    
    # Check how different the DEM is from the RP elevation
    # gw_elevation.spatial$feetmsl_from_dem <- extract(dem_watershed, gw_elevation.spatial, method='bilinear')*3.28084
    # gw_elevation.spatial$difference_vs_dem <- gw_elevation.spatial$feetmsl_from_dem - as.numeric(gw_elevation.spatial$gp_elev_ft)
    # View(gw_elevation.spatial@data)
    # hist(gw_elevation.spatial$difference_vs_dem, breaks=40)
    
    # Remove elevations if RP and DEM are more than +/- 8 feet
    # gw_elevation.spatial <- subset(gw_elevation.spatial, gw_elevation.spatial$difference_vs_dem <=8 &
    #                                  gw_elevation.spatial$difference_vs_dem >=-8)
    
    #Subset data - set more specific date window
    season_spread = 7 # 0 => 1-day season; 1 => 3-day season; 
    gw_elevation.spatial <- subset(gw_elevation.spatial,
                                   gw_elevation.spatial$date >= (as.Date(median(gw_elevation.spatial$date)) 
                                                                 - as.difftime(season_spread, unit="days")) &
                                     gw_elevation.spatial$date <= (as.Date(median(gw_elevation.spatial$date)) 
                                                                   + as.difftime(season_spread, unit="days")) )
    return(gw_elevation.spatial)
  }
  
  
  
if("return_raster" %in% map_output){
  gw_elevation.spatial <- gw_elevation_subsetting(year, season)
  
    # Pre-crop the DEM
  if(crop_to_data == TRUE){
    gw_elevation.convex <- gConvexHull(gw_elevation.spatial)
    gw_elevation.convex <- gUnaryUnion(gw_elevation.convex)
    gw_elevation.convex <-  gBuffer(gw_elevation.convex, width=500)
    dem_contour_boundary <- crop(dem_watershed, gw_elevation.convex)
  } else {dem_contour_boundary <- dem_watershed}
  
  
  if(datum=="msl"){
    #generate Thin Plate Spline model object
    gw_elevation_subsetting(year, season)
    gw_elevation.tps <- Tps(gw_elevation.spatial@coords, as.numeric(gw_elevation.spatial$wse_ft), verbose=FALSE)
  }
  if(datum=="gs"){
    gw_elevation_subsetting(year, season)
    gw_elevation.tps <- Tps(gw_elevation.spatial@coords, as.numeric(gw_elevation.spatial$gs_to_ws_ft), verbose=FALSE)
    
  }
  
  #Interpolate spline to all points in basin DEM raster
  gw_elevation.interpolate <- interpolate(dem_contour_boundary, gw_elevation.tps)
  
  # Mask interpolated raster to groundwater basin boundary
  # gw_elevation.interpolate <- mask(gw_elevation.interpolate, boundary_basin)
  # to do : maybe take out above statement, since we crop to basin below
  gw_elevation.raster <- gw_elevation.interpolate
  
  #Crop Raster to the convex hull (largest polygon covered, + a buffer) of our water measurement data
  if(crop_to_data == TRUE){
    gw_elevation.convex <- gConvexHull(gw_elevation.spatial)
    buf1 <- gBuffer(gw_elevation.convex, width=500, byid=TRUE)
    buf2 <- gUnaryUnion(buf1)
    buf <-  gBuffer(buf2, width=500)
    gw_elevation.raster <- mask(gw_elevation.raster, buf)
  }
  
  #### crop map to basin
  gw_elevation.raster <- mask(crop(gw_elevation.raster,basin), basin)
}
  
  if(map_output=="return_spatial"){
  gw_elevation.spatial <- gw_elevation_subsetting(year, season)
  return(gw_elevation.spatial)
  }
  if(map_output=="return_raster"){
  return(gw_elevation.raster)
  }
}
groundwater_contour_figure = function(gw_elevation_raster, gw_elevation_spatial, datum, basin_name){

  con <- function(test, yes, no, filename="", ...) {
    stopifnot(inherits(test, "Raster"))
    if (!inherits(no, "Raster")) {
      x <- reclassify(test, rbind(c(0,no), c(1,NA)))    
    } else {
      x <- mask(no, test, maskvalue=TRUE)
    }
    if (!inherits(yes, "Raster")) {
      yes <- reclassify(test, rbind(c(1,yes), c(0,NA))) 
    }
    cover(x, yes, filename=filename, ...)
  }
  
  
  # tmap_options(max.raster = c(plot = 20000000, view = 20000000))

  if(datum=="msl"){
    #map colors to elevations (ftamsl) for each basin
    if(basin_name == "Scott River Valley"){
      cuts = c(2680, 2700, 2720, 2740, 2760, 2780, 2800, 2820, 2840, 2890, 2940)}
    if(basin_name == "Shasta Valley"){cuts = seq(2450, 2850, 50)}
    if(basin_name == "Butte Valley"){cuts = seq(4100, 4240, 20)}
  }
  
  if(datum=="gs"){
    #map colors to elevations (ftamsl) for each basin
    if(basin_name == "Scott River Valley"){cuts = seq(0, 140, 20)}
    if(basin_name == "Shasta Valley"){cuts = seq(0, 140, 20)}
    if(basin_name == "Butte Valley"){cuts = seq(0, 150, 20)}
  }
  
  if(datum=="msl"){contour_pal <- colorRampPalette(c("red","orange", "yellow","white","blue"))}
  if(datum=="gs"){contour_pal <- colorRampPalette(c("blue","white", "yellow","orange","red"))}
  
  gw_elevation_raster <- con(gw_elevation_raster > 0.0 , gw_elevation_raster , 0)
  
  
  gw_elevation.contour <- rasterToContour(gw_elevation_raster, levels = cuts)
  
  contour_pal(length(cuts)-1)
  
  
  tm_shape(hill_basin) +

    tm_raster(palette = dem_shade, n = 100, legend.show = FALSE) +
    tm_shape(basin, name = "Groundwater Basin",is.master = T, unit=mapunits) + tm_borders(color_basin, lwd = 1, lty=2) +
    tm_shape(road_main, name = road_main$FULLNAME, unit=mapunits) + tm_lines(color_main_road, lwd = 3) +  tm_text("FULLNAME")+
    tm_shape(cities) + 
      tm_polygons(color_cities, lwd=1, alpha = 0.2) +
    tm_shape(gw_elevation_raster) + tm_raster(alpha = 0.5, palette = contour_pal(length(cuts)-1), legend.show = FALSE)+
    tm_shape(gw_elevation.contour) + 
      tm_iso(remove.overlap = TRUE, along.lines = TRUE, overwrite.lines = TRUE) + tm_text("level", size = 1.5) +
    tm_shape(gw_elevation_spatial) + 
      tm_dots(col="black", size=0.5, alpha=0.2 )+ 
      tm_text("gs_to_ws_ft", size=0.8, alpha = 0.8, auto.placement = TRUE)+
      # tm_text("well_code", size=0.8, auto.placement = TRUE) +
    tm_shape(cities_centroid, name = "Towns", unit=mapunits) +
    # tm_symbols(color_cities, border.lwd=NA, size = 0.5) +
      tm_text("name", size = 0.8, xmod = cities_centroid$label_xmod, ymod = cities_centroid$label_ymod, col=color_cities_text) +
    tm_scale_bar()+
    tm_compass(position = c("left", "top"), type = "4star", size = 2)+
    tm_add_legend(type="line", lwd = c(1,1), col =  c(color_watershed, color_basin), 
                  labels = c("Watershed", "Groundwater Basin"),
                  lty=c(1,2)) +
    tm_add_legend(type="fill", alpha=0.2, col=c(color_cities), labels=c("Cities"))+
    # tm_add_legend(type="fill", alpha=0.2, col=c(contour_pal(length(cuts))), labels=c(paste(cuts, "Feet Below Ground")), reverse=FALSE)+
    tm_add_legend(type="symbol", col = color_cities, border.lwd = NA, size = 0.5, labels = "Towns")+
    tm_add_legend(type = "line", lwd = c(3), col = c(color_main_road), labels = c("Highway 97")) +
    tm_layout(legend.bg.color = "white", legend.frame = T)
  
  
  
  
}



gw_elevation.raster_spring_2018 <- contour_function(wl_obs, 
             year = 2018,
             season = "spring", 
             map_output = c("return_raster"), 
             crop_to_data = TRUE,
             datum="gs")
gw_elevation.spatial_spring_2018 <- contour_function(wl_obs, 
             year = 2018,
             season = "spring", 
             map_output = c("return_spatial"), 
             crop_to_data = TRUE,
             datum="gs")
gw_figure <- groundwater_contour_figure(gw_elevation_raster=gw_elevation.raster_spring_2018, 
                     gw_elevation_spatial=gw_elevation.spatial_spring_2018, 
                     datum="gs", 
                     basin_name="Butte Valley")
gw_figure 

ttm()
gw_figure

Source data

The data used in this plot comes from multiple sources but is available through the California Open Data Portal.

Example prints of data

head(wl_obs)
##   stn_id          well_code wlm_id       date rp_elev_ft gp_elev_ft
## 1  22363 414796N1223568W001 675900 2008-10-30     3344.6     3343.6
## 2  22363 414796N1223568W001 675899 2008-04-04     3344.6     3343.6
## 3  22363 414796N1223568W001 675897 2007-03-28     3344.6     3343.6
## 4  22363 414796N1223568W001 675896 2006-10-19     3344.6     3343.6
## 5  22363 414796N1223568W001 675895 2006-04-20     3344.6     3343.6
## 6  22363 414796N1223568W001 675894 2005-10-13     3344.6     3343.6
##   rdng_ws reading_rp_ft wse_ft rpe_wse gs_to_ws_ft wlm_qa_desc wlm_desc
## 1       0         242.9 3101.7   242.9       241.9        <NA>  Unknown
## 2       0         242.1 3102.5   242.1       241.1        <NA>  Unknown
## 3       0         241.2 3103.4   241.2       240.2        <NA>  Unknown
## 4       0         241.3 3103.3   241.3       240.3        <NA>  Unknown
## 5       0         242.0 3102.6   242.0         241        <NA>  Unknown
## 6       0         242.8 3101.8   242.8       241.8        <NA>  Unknown
##                      wlm_acc_desc wlm_org_id                  wlm_org_name
## 1 Water level accuracy is unknown          1 Department of Water Resources
## 2 Water level accuracy is unknown          1 Department of Water Resources
## 3 Water level accuracy is unknown          1 Department of Water Resources
## 4 Water level accuracy is unknown          1 Department of Water Resources
## 5 Water level accuracy is unknown          1 Department of Water Resources
## 6 Water level accuracy is unknown          1 Department of Water Resources
##   comments coop_agency_org_id                 coop_org_name
## 1     <NA>                  1 Department of Water Resources
## 2     <NA>                  1 Department of Water Resources
## 3     <NA>                  1 Department of Water Resources
## 4     <NA>                  1 Department of Water Resources
## 5     <NA>                  1 Department of Water Resources
## 6     <NA>                  1 Department of Water Resources
##   data_table_source
## 1  DWR_Periodic_GWL
## 2  DWR_Periodic_GWL
## 3  DWR_Periodic_GWL
## 4  DWR_Periodic_GWL
## 5  DWR_Periodic_GWL
## 6  DWR_Periodic_GWL
head(wl_obs)
##   stn_id          well_code wlm_id       date rp_elev_ft gp_elev_ft
## 1  22363 414796N1223568W001 675900 2008-10-30     3344.6     3343.6
## 2  22363 414796N1223568W001 675899 2008-04-04     3344.6     3343.6
## 3  22363 414796N1223568W001 675897 2007-03-28     3344.6     3343.6
## 4  22363 414796N1223568W001 675896 2006-10-19     3344.6     3343.6
## 5  22363 414796N1223568W001 675895 2006-04-20     3344.6     3343.6
## 6  22363 414796N1223568W001 675894 2005-10-13     3344.6     3343.6
##   rdng_ws reading_rp_ft wse_ft rpe_wse gs_to_ws_ft wlm_qa_desc wlm_desc
## 1       0         242.9 3101.7   242.9       241.9        <NA>  Unknown
## 2       0         242.1 3102.5   242.1       241.1        <NA>  Unknown
## 3       0         241.2 3103.4   241.2       240.2        <NA>  Unknown
## 4       0         241.3 3103.3   241.3       240.3        <NA>  Unknown
## 5       0         242.0 3102.6   242.0         241        <NA>  Unknown
## 6       0         242.8 3101.8   242.8       241.8        <NA>  Unknown
##                      wlm_acc_desc wlm_org_id                  wlm_org_name
## 1 Water level accuracy is unknown          1 Department of Water Resources
## 2 Water level accuracy is unknown          1 Department of Water Resources
## 3 Water level accuracy is unknown          1 Department of Water Resources
## 4 Water level accuracy is unknown          1 Department of Water Resources
## 5 Water level accuracy is unknown          1 Department of Water Resources
## 6 Water level accuracy is unknown          1 Department of Water Resources
##   comments coop_agency_org_id                 coop_org_name
## 1     <NA>                  1 Department of Water Resources
## 2     <NA>                  1 Department of Water Resources
## 3     <NA>                  1 Department of Water Resources
## 4     <NA>                  1 Department of Water Resources
## 5     <NA>                  1 Department of Water Resources
## 6     <NA>                  1 Department of Water Resources
##   data_table_source
## 1  DWR_Periodic_GWL
## 2  DWR_Periodic_GWL
## 3  DWR_Periodic_GWL
## 4  DWR_Periodic_GWL
## 5  DWR_Periodic_GWL
## 6  DWR_Periodic_GWL
dem_watershed
## class      : RasterLayer 
## dimensions : 2177, 1632, 3552864  (nrow, ncol, ncell)
## resolution : 23.2, 30.8  (x, y)
## extent     : -182358, -144495.6, 382879.4, 449931  (xmin, xmax, ymin, ymax)
## crs        : +proj=aea +lat_1=34 +lat_2=40.5 +lat_0=0 +lon_0=-120 +x_0=0 +y_0=-4000000 +datum=NAD83 +units=m +no_defs +ellps=GRS80 +towgs84=0,0,0 
## source     : memory
## names      : USGS_NED_one_arcsec_SiskiyouExtent3310 
## values     : 818.9161, 2829.046  (min, max)
wsh
## class       : SpatialPolygonsDataFrame 
## features    : 1 
## extent      : -182349.4, -144492.3, 382882.8, 451771  (xmin, xmax, ymin, ymax)
## crs         : +proj=aea +lat_1=34 +lat_2=40.5 +lat_0=0 +lon_0=-120 +x_0=0 +y_0=-4000000 +datum=NAD83 +units=m +no_defs +ellps=GRS80 +towgs84=0,0,0 
## variables   : 15
## names       : OBJECTID,                                  TNMID, MetaSource, SourceData, SourceOrig, SourceFeat,   LoadDate, GNIS_ID, AreaAcres, AreaSqKm, States,     HUC8,  Name,    Shape_Leng,     Shape_Area 
## value       :        1, {522489C1-3A24-4D0F-8F85-83A71EB7D499},         NA,         NA,         NA,         NA, 2012/06/11,       0, 388548.36,   1572.4,  CA,OR, 18010205, Butte, 2.33088722827, 0.170162999863
basin
## class       : SpatialPolygonsDataFrame 
## features    : 1 
## extent      : -178047.4, -154063.6, 416200.7, 444544.4  (xmin, xmax, ymin, ymax)
## crs         : +proj=aea +lat_1=34 +lat_2=40.5 +lat_0=0 +lon_0=-120 +x_0=0 +y_0=-4000000 +datum=NAD83 +units=m +no_defs +ellps=GRS80 +towgs84=0,0,0 
## variables   : 7
## names       : OBJECTID, Basin_Numb, Basin_Subb,   Basin_Name,   Basin_Su_1, Region_Off,                               GlobalID 
## value       :      399,      1-003,      1-003, BUTTE VALLEY, BUTTE VALLEY,        NRO, {374607EF-33B2-4FA6-8118-C5C1C0B050D8}
cities
## class       : SpatialPolygonsDataFrame 
## features    : 4 
## extent      : -167802, -158731.9, 396901.3, 441209  (xmin, xmax, ymin, ymax)
## crs         : +proj=aea +lat_1=34 +lat_2=40.5 +lat_0=0 +lon_0=-120 +x_0=0 +y_0=-4000000 +datum=NAD83 +units=m +no_defs +ellps=GRS80 +towgs84=0,0,0 
## variables   : 16
## names       : STATEFP, PLACEFP,  PLACENS,   GEOID,    NAME,    NAMELSAD, LSAD, CLASSFP, PCICBSA, PCINECTA, MTFCC, FUNCSTAT,   ALAND, AWATER,    INTPTLAT, ... 
## min values  :      06,   19584, 02408156, 0619584,  Dorris, Dorris city,   25,      C1,       N,        N, G4110,        A, 1819372,      0, +41.5789379, ... 
## max values  :      06,   78176, 02410347, 0678176, Tennant, Tennant CDP,   57,      U1,       N,        N, G4210,        S,  628135,  40653, +41.9649654, ...
hill_basin
## class      : RasterLayer 
## dimensions : 920, 1034, 951280  (nrow, ncol, ncell)
## resolution : 23.2, 30.8  (x, y)
## extent     : -178042.8, -154054, 416205, 444541  (xmin, xmax, ymin, ymax)
## crs        : +proj=aea +lat_1=34 +lat_2=40.5 +lat_0=0 +lon_0=-120 +x_0=0 +y_0=-4000000 +datum=NAD83 +units=m +no_defs +ellps=GRS80 +towgs84=0,0,0 
## source     : memory
## names      : layer 
## values     : 0.1856007, 0.9998591  (min, max)
color_cities
## [1] "#d7922d"
cities_centroid
## class       : SpatialPointsDataFrame 
## features    : 4 
## extent      : -167197.3, -159499.4, 397686.6, 440097  (xmin, xmax, ymin, ymax)
## crs         : +proj=aea +lat_1=34 +lat_2=40.5 +lat_0=0 +lon_0=-120 +x_0=0 +y_0=-4000000 +datum=NAD83 +units=m +no_defs +ellps=GRS80 +towgs84=0,0,0 
## variables   : 3
## names       :    name, label_xmod, label_ymod 
## min values  :  Dorris,       -1.2,       -0.5 
## max values  : Tennant,        2.2,       -0.5