Tools
Troubleshooting
Keywords
Acknowledgment
Disclaimer
The code can be extended with new parameters (e.g., direct shear stress output from the numerical model), new analyses (e.g., a new shear velocity calculation method / law), and features (e.g., another plant species or block ramps).
The Rasters creation results from and functions that are stored in cLifespanDesignAnalysis.py
. functions create Rasters with lifespan data (0 to 20 years in the sample case) and functions create Rasters with design parameters such as the required stables grain size of angular boulders (rocks).
Class names start with an upper case letter and do not contain any special characters, also excluding dash or underscore signs. Instantiations of classes are all lower case letters. Features, Parameters, and Analysis classes are stored in separate files called cFeatureLifespan.py
, cParameters.py
and cLifespanDesignAnalysis.py
, respectively. In addition, Feature classes may inherit subfeature classes from files names cSubfeature.py
, for example, cPlants.py
.
Function names consist of lower case letters only and the underscore sign “_” separates words.
All class names, variable names, and function names are in alphabetic order (a = up, z = down), except the s, which determine the parameter run hierarchy.
The best position of restoration features and their lifespans depend on multiple parameters in most cases. The output Rasters (lifespan maps) are computed in by batch-processing every parameter (i.e., one parameter map is processed after another). This batch processing strictly follows the below-listed hierarchy:
Flow depth Rasters (dimensional) starting with the lowest discharge to the highest discharge
Internal Raster name: ras_hQQQQQQ
Flow velocity Rasters (dimensional) starting with the lowest discharge to the highest discharge
Internal Raster name: ras_uQQQQQQ
Hydraulic Rasters (dimensionless)
Internal Raster name: ras_taux
(dimensionless bed shear stress) or ras_Fr
(Froude number); if needed: the hierarchy among the dimensionless hydraulic numbers is not important
Mobile bed, fine sediment, and stable grain size Raster analysis
Internal Raster name: ras_Dcr
(mobile or stable grain size)
Topographic change Rasters
Internal Raster names: ras_fill
(fill Raster only), ras_scour
(scour only) or ras_tcd
(combined fill and scour)
Detrended DEM Raster analysis
Internal Raster name: ras_det
(relevant, e.g., for berm setback)
Morphological Unit Rasters
Internal Raster name: ras_mu
Side channel delineation
Internal Raster name: ras_sch
Depth to water table
Internal Raster name: ras_d2w
(relevant, e.g., for plantings and terrain grading)
The dimensional hydraulic maps need to be invoked before any other analysis is performed because the u and h maps are the only ones that entirely cover the area of interest, without “noData
” pixels.
Every feature
has a feature.parameter_list
attribute containing a list of parameters that determine the feature lifespan and applicability space. The parameters are ordered in the feature.parameter_list
according to the hierarchy. Once the last element of feature.parameter_list
is processed and stored in the cache folder, the code exits the loop and copies the last ras_parameter
to the Output/Rasters/condition/
folder. This copy is renamed lf_shortname
, where the usage of shortnames is necessary because arcpy
cannot save or copy Raster with names exceeding 13 characters when GRID Rasters are used (even though the primary Raster type if GeoTIFF).
The currently implemented parameters are listed in the parameters section. New parameters may require new input Rasters in addition to the default Rasters. The Rasters need to be saved in the folder 01_Conditions/CONDITION/
using the GeoTIFF (or Esri GRID) format. Other Raster formats will result in errors when the code attempts to save the final Rasters. The template for creating a new parameter class is shown in the box. Use the following workflow to implement a new parameter in the code:
Create GeoTIFF parameter Rasters in the folder 01_Conditions/CONDITION/
.
Add a new parameter class in the file cParameters.py
(see below explanations).
Add a new function called analyse_parameter
to the ArcPyAnalysis
class in the file (see the add-analysis section ) or change existing analysis for using the new parameter.
Add new parameter class to cParameters.py
EXPRESSIONS
as indicatedcParameters.py
; e.g., the class Mypar
should be placed below the existing classes GrainSizes
and WaterTable
class PARAMETERNAME():
def __init__(self, condition):
self.condition=condition # [str] planning situation, .e.g., "2008"
self.raster_path='YOUR PATH/01_Conditions/'
self.raster_names=['RASTER1', 'RASTER2', ..., 'RASTERi', ..., 'RASTERn']
self.RAS1=arcpy.Raster(self.raster_path+self.condition+'/'+self.raster_names[0])
self.RAS2=arcpy.Raster(self.raster_path+self.condition+'/'+self.raster_names[1])
...
self.RASi=arcpy.Raster(self.raster_path+self.condition+'/'+self.raster_names[i-1])
...
self.RASn=arcpy.Raster(self.raster_path+self.condition+'/'+self.raster_names[n-1])
The analysis routines are differentiated between and -functions, which are contained in the file cLifespanDesignAnalysis.py
.
analyse_
-functions return Rasters containing estimated survival times (in years) or on/off values (1/0). An analyse_
-function will always try to find existing Rasters produced from previous analysis functions according to the analysis hierarchy unless a dimensional hydraulic analysis (u
, h
or their combination) is performed. For this reason, analyse_
-functions use the verify_raster_info
-function to look up for previous analyses that are stored in raster_dict_lf
. At the end of an analyse_
-function, raster_dict_lf
is updated using raster_dict_lf.update("ras_current")
. This serial Raster analysis produces lifespan Rasters, which can be regardlessly converted to design Rasters by the save_manager
-function when the feature properties are set to self.ds = True
while self.lf = False
(read more about adding features).
design_
-functions produce Rasters containing specific parameter values, such as the critical grain size in inches. A design_
-function will update the raster_dict_ds
-dictionary which is passed to the save_manager
-function when the feature variable self.ds = True
.
The major difference between the raster_dict_lf
and raster_dict_ds
-dictionaries is that the save_manager()
saves only the last hierarchy-based entry of to produced lifespan Rasters but all entries of raster_dict_ds
to produced design Rasters. The combination of multiple parameters into one design Raster can be achieved anyway by setting self.ds = True
while self.lf = False
(see add features section). The latter settings convert lifespan Rasters to design
Rasters.
Use the following workflow to implement a new parameter in the code:
Ensure that all required parameters are available (see the parameter list and Add parameters section).
Create an identifier string of 2 to 3 characters; the following explanations refer to a dummy identifier named NEW
(replace with lowercase letters).
Add a new analyse_NEW
or design_NEW
-function in the file cLifespanDesignAnalysis.py
(cf. code example below).
In cFeatureLifespan.py
ensure that concerned features have the
following properties:
The feature.paramter_list
needs to contain the new analysis’ identifier (NEW
)
All required threshold values are defined (feature.threshold_NEW1 = ...
)
In featureanalysis.py
, add a call of the new function:
if parameter_name == "NEW":
feature_analysis.analyse_NEW(feature.threshold_NEW1, ... )
The template for a new analyse_NEW
-function in the file cLifespanDesignAnalysis.py
starts with the general statement of unit conversion (controlled by user input) and continues as follows (pay attention on indentation):
def analyse_NEW(self, threshold_NEW1, ...):
# Lines where changes are required are tagged with #--CHANGE--#
# Convert length units of threshold values
threshold_LENGTH = threshold_LENGTH * self.ft2m #--CHANGE--#
try:
arcpy.CheckOutExtension('Spatial') # check out license
arcpy.gp.overwriteOutput = True
arcpy.env.workspace = self.cache
self.logger.info(" >>> Analyzing NEW.") #--CHANGE--#
parameter1 = PARAMETER1(self.condition) #--CHANGE--#
parameter2 = PARAMETER2(self.condition) #--CHANGE--#
... #--CHANGE--#
self.ras_NEW = calculation with parameter1, parameter2, ... threshold_NEW1, ... #--CHANGE--#
self.ras_LF = self.compare_raster_set(parameter_ras, threshold)
# verify existing analyses
if self.verify_raster_info():
self.logger.info(" based on raster: " + self.raster_info_lf)
# make temp_ras without noData pixels
temp_ras_NEW = Con((IsNull(self.ras_NEW) == 1), (IsNull(self.ras_NEW) * some_factor), self.ras_NEW) #--CHANGE--#
# compare temp_ras with raster_dict but use self.ras_... values if condition is True
ras_NEW_update = Con((temp_ras_NEW == 1), self.ras_NEW, self.raster_dict_lf[self.raster_info_lf]) #--CHANGE--#
self.ras_NEW = ras_NEW_update #--CHANGE--#
# update lf dictionary
self.raster_info_lf = "ras_NEW" #--CHANGE--#
self.raster_dict_lf.update({self.raster_info_lf: self.raster_info_lf})
arcpy.CheckInExtension('Spatial')
except arcpy.ExecuteError:
self.logger.info("ExecuteERROR: (arcpy) in NEW analysis.") #--CHANGE--#
self.logger.info(arcpy.GetMessages(2))
arcpy.AddError(arcpy.GetMessages(2))
except Exception as e:
self.logger.info("ExceptionERROR: (arcpy) in NEW analysis.") #--CHANGE--#
self.logger.info(e.args[0])
arcpy.AddError(e.args[0])
except:
self.logger.info("ERROR: (arcpy) in NEW analysis.") #--CHANGE--#
self.logger.info(arcpy.GetMessages())
The template for a new -function in the file cLifespanDesignAnalysis.py
is as follows (pay attention on indentation):
def design_NEW(self, threshold_NEW1, ...):
# Lines where changes are required are tagged with #--CHANGE--#
try:
arcpy.CheckOutExtension('Spatial') # check out license
arcpy.gp.overwriteOutput = True
arcpy.env.workspace = self.cache
self.logger.info(" >>> Designing NEW.") #--CHANGE--#
parameter1 = PARAMETER1(self.condition) #--CHANGE--#
parameter2 = PARAMETER2(self.condition) #--CHANGE--#
... #--CHANGE--#
self.ras_NEW1 = calculation with parameter1, parameter2, ... threshold_NEW1, ... #--CHANGE--#
## if required add more design rasters (all need to be added to self.raster_dict_ds)
self.ras_NEWi = another (optional) calculation with parameter1, parameter2, ... threshold_NEW1, ... #--CHANGE--#
# update ds dictionary
self.raster_dict_ds.update({self.raster_info_lf: self.ras_NEW1}) #--CHANGE--#
# if required uncomment:
# self.raster_dict_ds.update({self.raster_info_lf: self.ras_NEWi}) #--CHANGE--#
arcpy.CheckInExtension('Spatial')
except arcpy.ExecuteError:
self.logger.info("ExecuteERROR: (arcpy) in NEW design.") #--CHANGE--#
self.logger.info(arcpy.GetMessages(2))
arcpy.AddError(arcpy.GetMessages(2))
except Exception as e:
self.logger.info("ExceptionERROR: (arcpy) in NEW design.") #--CHANGE--#
self.logger.info(e.args[0])
arcpy.AddError(e.args[0])
except:
self.logger.info("ERROR: (arcpy) in NEW design.") #--CHANGE--#
self.logger.info(arcpy.GetMessages())
The currently implemented features are listed in the Features page. New features can be implemented in the cFeatureLifespan.py
file using the following workflow:
Ensure that all required parameters are available (see the parameter list and Add parameters section).
Ensure that all required analysis and / or design functions are available (see Add analyses).
Choose a name for the new feature beginning with an uppercase letter followed by lowercase letters only; the name NewFeature
is subsequently used for illustrative purposes
cFeatureLifespan.py
modify the class RestorationFeature
:
def __init__(self, feature_name, ∗sub_feature )
:if feature_name == "NewFeature" and not(sub_feature):
` self.feature = NewFeature()<br/>
self.sub = False<br/>
self.name = feature_name`
Please note: the shortname should not have more than 6 characters; otherwise the code will cutoff the shortname automatically.
Both the __init__(self)
initiation and the NewFeature
instantiation are necessary to facilitate the external access to Methods and Properties.
A feature may have subfeatures (as for example class Plantings
). In this case, replace and not(sub_feature)
with and sub_feature
and set self.sub = True
.
Add a new class to cFeatureLifespan.py
according to the example below, considering the required hierarchically ordered self.parameter_list
, self.threshold_
and lifespan (self.lf=True/False)
/ design (self.ds=True/False)
Raster analysis properties.
Add new column in LifespanDesign/.templates/threshold_values.xlsx
and add the feature name as well as relevant threshold values.
Commit changes in RiverArchitect/ModifyTerrain/cDefinitions.py
:
Append the shortname to self.id_list = ["backwt", "widen", "grade", "sideca", "sidech", "elj", "fines", "box", "cot", "whi", "wil", "rocks", "gravin", "gravou", "cust"]
Append full feature name to self.name_ _list = ["Backwater", "Bermsetback (Widen)", "Grading", "Sidecavity", "Sidechannel", "Streamwood", "Finesediment", "Plantings: Box Elder", "Plantings: Cottonwood", "Plantings : White Alder", "Plantings: Willows", "Boulders/rocks", "Gravel replenishment", "Gravel stockpile ", "Custom DEM"]
Append feature threshold column (thresholdvalues.xlsx
) name in self.threshold_cols = ["E", "Q", "G", "O", "P", "R", "F", "J", "K", "L", "M", "N", "H", "I", "S"]
The template for a new NewFeature
-class in the file cFeatureLifespan.py
is as follows (pay attention on indentation), given that no subfeatures apply:
class Newfeature():
# This is the Newfeature class.
def __init__(self):
self.ds = False # identify if design map applies
self.lf = True # identify if lifespan map applies
self.parameter_list = ["PAR1", "PAR2", ..., "PARi", , "PARn"] # Respect Hierarchy -- example: PAR1 = "hyd"
self.shortname = "max6ch"
thresh = ThresholdDirector(self.shortname) # instantiate reader of threshold values
## uncomment and adapt follow line if PAR = mu applies
# self.mu_bad = thresh.get_thresh_value("mu_bad")
# self.mu_good = thresh.get_thresh_value("mu_good")
# self.mu_method = thresh.get_thresh_value("mu_method")
self.threshold_1 = thresh.get_thresh_value("ID_1")
self.threshold_2 = thresh.get_thresh_value("ID_2")
...
self.threshold_i = thresh.get_thresh_value("ID_i")
...
self.threshold_n = thresh.get_thresh_value("ID_n")
thresh.close_wb() # close threshold workbook
def __call__(self):
pass
Valid ID_i
strings are either string of: "mu bad", "mu good", "mu method","D", "d2w low", "d2w up", "det low", "det up", "fill", "Fr", "freq", "h", "inverse_tcd ", "scour", "sf", "taux", "u"
. The get_thresh_value("ID_i")
-function is a routine of the ThresholdDirector
class which is stored in LifespanDesign/cThresholdDirector.py
. Modifications of the ThresholdDirector
class are deprecated and threshold values should be modified in the spreadsheet LifespanDesign/.templates/threshold_values.xlsx
.
If the new feature has subfeatures, the following template applies:
class NewFeature(Subfeature_1, Subfeature_2, ..., Subfeature_i, ... Subfeature_n):
# This is the NewFeature class inheriting from Subfeature_1 to Subfeature_n.
def __init__(self, subfeature):
self.lf = True # identify if lifespan map applies
self.ds = False # identify if design map applies
if subfeature == 'subfeature_1':
Subfeature_1.__init__(self)
if subfeature == 'subfeature_2':
Subfeature_2.__init__(self)
if subfeature == 'subfeature_i':
Subfeature_i.__init__(self)
if subfeature == 'subfeature_n':
Subfeature_n.__init__(self)
def __call__(self):
pass
Then, the subfeature needs to be defined (e.g., in an external file called cNewSubFeature.py
). In this case, add from cNewSub
Feature import ∗
at the top of cFeatureLifespan.py
. Use the following class-template for each subfeature(SubFeature_1,..., SubFeature_n
). Please note that the lifespan self.lf = True / False
and design self.ds = True / False
properties are already assigned in the inheriting feature class.
class NewSubFeature_i():
# This is the NewSubFeature_i class.
def __init__(self):
self.parameter_list = ["PAR1", "PAR2", ..., "PARi", , "PARn"] # Respect Hierarchy!; example: PAR1 = "hyd"
self.shortname = "max6ch"
thresh = ThresholdDirector(self.shortname) # instantiate reader of threshold values
## uncomment and adapt follow line if PAR = mu applies
# self.mu_bad = thresh.get_thresh_value("mu_bad")
# self.mu_good = thresh.get_thresh_value("mu_good")
# self.mu_method = thresh.get_thresh_value("mu_method")
self.threshold_1 = thresh.get_thresh_value("ID_1")
self.threshold_2 = thresh.get_thresh_value("ID_2")
...
self.threshold_i = thresh.get_thresh_value("ID_i")
...
self.threshold_n = thresh.get_thresh_value("ID_n")
thresh.close_wb() # close threshold workbook
def __call__(self):
pass