smashbox.model.mesh
1import smash 2import pandas as pd 3import geopandas as gpd 4import os 5import numpy as np 6from smashbox.init.param import smashboxparam 7import copy 8 9 10class mesh: 11 """Class mesh(). This class has three functions and one attributes to generate, load, 12 write and store the mesh used by Smash.""" 13 14 def __init__(self, setup): 15 16 self._setup = setup 17 18 self.mesh = None 19 self.catchment_polygon = None 20 21 def write_mesh(self, filename: os.PathLike | None = None): 22 """ 23 Write the mesh of the Smash model unsing hdf5 format. 24 :param filename: Path to the file where to write the mesh, defaults to None 25 :type filename: os.PathLike | None, optional 26 27 """ 28 if filename is not None: 29 smash.io.save_mesh(self.mesh, path=filename) 30 else: 31 raise ValueError(f"Output filename '{filename}' is None.") 32 33 def load_mesh(self, filename: os.PathLike | None = None): 34 """ 35 Read a mesh for Smash stored with the hdf5 format. 36 :param filename: path to the hdf5 file 37 :type filename: TYPE 38 39 """ 40 41 if os.path.exists(filename): 42 self.mesh = smash.io.read_mesh(filename) 43 else: 44 raise ValueError(f"{filename} does not exist.") 45 46 def generate_mesh( 47 self, 48 param: smashboxparam | None = None, 49 query: str | None = None, 50 max_depth: float = 1.0, 51 area_error_th: None | float = None, 52 lacuna_threshold: None | float = None, 53 ): 54 """ 55 :param param: Class param.smashboxparam(), store main smashbox parameters 56 :type param: param.smashboxparam() 57 :param query: Any pandas dataframe query as string: '(SURF>20) & (SURF<100)'. This query 58 must be build using the field (column name) in the outlet database. 59 https://pandas.pydata.org/docs/user_guide/indexing.html#the-query-method 60 :type query: str 61 :max_depth: The maximum depth accepted by the algorithm to find the catchment outlet. 62 A **max_depth** of 1 means that the algorithm will search among the 63 combinations in 64 (``row - 1``, ``row``, ``row + 1``; ``col - 1``, ``col``, ``col + 1``), 65 the coordinates that minimize 66 the relative error between the given catchment area and the modeled 67 catchment area calculated from the 68 flow directions file. 69 :type `int`, default 1 70 :param area_error_th: Tolerance error during the positionning of the outlets. If the Error `(Ssim-Sobs)/Sobs > area_error_th`, the outlet will be excluded. 71 :type area_error_th: float 72 :param lacuna_threshold: Lacuna threshold for the discharges in percent. All gauge where the proportion of lacuna exceed this threshold will be removed. 73 :type: float | Nonetype 74 75 """ 76 77 if param.bbox is not None: 78 bbox = [ 79 param.bbox["left"], 80 param.bbox["right"], 81 param.bbox["bottom"], 82 param.bbox["top"], 83 ] 84 else: 85 bbox = None 86 87 if not os.path.exists(param.outlets_database): 88 param.outlets_database = os.path.join( 89 param.asset_dir, "outlets", param.outlets_database 90 ) 91 92 if os.path.exists(param.outlets_database): 93 stations_calage = pd.read_csv(param.outlets_database) 94 else: 95 raise ValueError(f"</> Error: file {param.outlets_database} not found") 96 97 # Pointeur ou copy of param.outletsID ? ici pointeur, ca veux dire que les stations enlevé du mesh sont aussi enlevé de param 98 Input_outletsID = param.outletsID 99 100 if param.outletsID is not None: 101 if len(param.outletsID) > 0: 102 stations_calage = stations_calage.loc[ 103 stations_calage[param.outlets_database_fields["id"]].isin( 104 param.outletsID 105 ) 106 ] 107 108 # check and print outlets not in database 109 for index, id_outlets in enumerate(Input_outletsID): 110 111 if ( 112 id_outlets 113 not in stations_calage[param.outlets_database_fields["id"]].values 114 ): 115 116 print( 117 f"</> Warning: Outlets `{Input_outletsID.pop(index)}` does not exist in the outlets database. This outlets will not be included in the mesh..." 118 ) 119 120 if query is not None: 121 stations_calage = stations_calage.query(query) 122 123 if bbox is not None: 124 stations_calage = stations_calage[ 125 ( 126 stations_calage[param.outlets_database_fields["coord_x"]] 127 >= param.bbox["left"] 128 ) 129 & ( 130 stations_calage[param.outlets_database_fields["coord_x"]] 131 <= param.bbox["right"] 132 ) 133 & ( 134 stations_calage[param.outlets_database_fields["coord_y"]] 135 >= param.bbox["bottom"] 136 ) 137 & ( 138 stations_calage[param.outlets_database_fields["coord_y"]] 139 <= param.bbox["top"] 140 ) 141 ] 142 143 # check and print outlets exluded outside bbox 144 for index, id_outlets in enumerate(Input_outletsID): 145 if ( 146 id_outlets 147 not in stations_calage[param.outlets_database_fields["id"]].values 148 ): 149 print( 150 f"</> Warning: Outlets `{Input_outletsID.pop(index)}` outside the boundingbox {bbox}. This outlets will not be included in the mesh..." 151 ) 152 153 if len(stations_calage) == 0: 154 print( 155 f"</> Error: outlets {param.outletsID} not found in" 156 "{param.outlets_database}" 157 ) 158 raise ValueError( 159 f"</> Error: outlets {param.outletsID} not found in" 160 "{param.outlets_database}" 161 ) 162 163 columns = { 164 "coord_x": param.outlets_database_fields["coord_x"], 165 "coord_y": param.outlets_database_fields["coord_y"], 166 "area": param.outlets_database_fields["area"], 167 "id": param.outlets_database_fields["id"], 168 } 169 170 # fist build 171 self.mesh = smash.factory.generate_mesh( 172 flwdir_path=param.flowdir, 173 bbox=bbox, 174 x=np.array(stations_calage[columns["coord_x"]][:]), 175 y=np.array(stations_calage[columns["coord_y"]][:]), 176 area=np.array( 177 stations_calage[columns["area"]][:] * 1e6 178 ), # Convert km² to m² 179 code=np.array(stations_calage[columns["id"]][:]), 180 epsg=param.epsg, 181 shp_path=param.outlets_shapefile, 182 max_depth=max_depth, 183 area_error_th=area_error_th, 184 ) 185 186 if lacuna_threshold is not None: 187 setup = copy.deepcopy(self._setup.setup) 188 setup.update( 189 { 190 "read_prcp": False, 191 "read_pet": False, 192 "read_qobs": True, 193 "adjust_interception": False, 194 "compute_mean_atmos": False, 195 } 196 ) 197 198 # filter gauge if lacuna exceed a thresholds 199 model = smash.Model(setup, self.mesh) 200 qobs = model.response_data.q 201 202 valid_gauge = model.mesh.code[ 203 np.where( 204 np.sum(qobs > 0.0, axis=1) / qobs.shape[1] * 100.0 205 >= (100.0 - lacuna_threshold) 206 ) 207 ] 208 209 unvalid_gauge = model.mesh.code[ 210 np.where( 211 np.sum(qobs < 0.0, axis=1) / qobs.shape[1] * 100.0 212 > lacuna_threshold 213 ) 214 ] 215 216 if len(unvalid_gauge) > 0: 217 print( 218 f"</> Remove gauges from the mesh where total lacuna " 219 f"between {setup['start_time']} and {setup['end_time']} " 220 f"exceed {lacuna_threshold}%: {unvalid_gauge}" 221 ) 222 223 stations_calage = stations_calage.loc[ 224 stations_calage[columns["id"]].isin(valid_gauge) 225 ] 226 227 if len(stations_calage) == 0: 228 print("</> Warnings, no outlets/gauge will be added to the mesh !") 229 230 # rebuild mesh 231 self.mesh = smash.factory.generate_mesh( 232 flwdir_path=param.flowdir, 233 bbox=bbox, 234 x=np.array(stations_calage[columns["coord_x"]][:]), 235 y=np.array(stations_calage[columns["coord_y"]][:]), 236 area=np.array( 237 stations_calage[columns["area"]][:] * 1e6 238 ), # Convert km² to m² 239 code=np.array(stations_calage[columns["id"]][:]), 240 epsg=param.epsg, 241 shp_path=param.outlets_shapefile, 242 max_depth=max_depth, 243 area_error_th=area_error_th, 244 ) 245 246 if param.outlets_shapefile is not None: 247 print("</> Outlets shapefile detected. Loading outlets ...") 248 if param.outlets_database_fields["id_shapefile"] != "None": 249 col_id = param.outlets_database_fields["id_shapefile"] 250 code_shape_file = [] 251 for i in range(len(self.mesh["code"])): 252 sta = self.mesh["code"][i] 253 code_shape_file.extend( 254 stations_calage.loc[ 255 stations_calage[columns["id"]] == sta, col_id 256 ].to_list() 257 ) 258 code_shape_file = np.array(code_shape_file) 259 else: 260 code_shape_file = self.mesh["code"] 261 262 catchment_polygon = gpd.read_file(param.outlets_shapefile) 263 self.catchment_polygon = catchment_polygon.loc[ 264 catchment_polygon.code.isin(code_shape_file) 265 ] 266 del catchment_polygon 267 268 else: 269 stations_calage = pd.DataFrame(None) 270 271 if bbox is None: 272 raise ValueError( 273 "Bbox is None. If no outlets provided, the bbox must be defined." 274 ) 275 276 self.mesh = smash.factory.generate_mesh( 277 flwdir_path=param.flowdir, bbox=bbox, epsg=param.epsg, max_depth=max_depth 278 )
class
mesh:
11class mesh: 12 """Class mesh(). This class has three functions and one attributes to generate, load, 13 write and store the mesh used by Smash.""" 14 15 def __init__(self, setup): 16 17 self._setup = setup 18 19 self.mesh = None 20 self.catchment_polygon = None 21 22 def write_mesh(self, filename: os.PathLike | None = None): 23 """ 24 Write the mesh of the Smash model unsing hdf5 format. 25 :param filename: Path to the file where to write the mesh, defaults to None 26 :type filename: os.PathLike | None, optional 27 28 """ 29 if filename is not None: 30 smash.io.save_mesh(self.mesh, path=filename) 31 else: 32 raise ValueError(f"Output filename '{filename}' is None.") 33 34 def load_mesh(self, filename: os.PathLike | None = None): 35 """ 36 Read a mesh for Smash stored with the hdf5 format. 37 :param filename: path to the hdf5 file 38 :type filename: TYPE 39 40 """ 41 42 if os.path.exists(filename): 43 self.mesh = smash.io.read_mesh(filename) 44 else: 45 raise ValueError(f"{filename} does not exist.") 46 47 def generate_mesh( 48 self, 49 param: smashboxparam | None = None, 50 query: str | None = None, 51 max_depth: float = 1.0, 52 area_error_th: None | float = None, 53 lacuna_threshold: None | float = None, 54 ): 55 """ 56 :param param: Class param.smashboxparam(), store main smashbox parameters 57 :type param: param.smashboxparam() 58 :param query: Any pandas dataframe query as string: '(SURF>20) & (SURF<100)'. This query 59 must be build using the field (column name) in the outlet database. 60 https://pandas.pydata.org/docs/user_guide/indexing.html#the-query-method 61 :type query: str 62 :max_depth: The maximum depth accepted by the algorithm to find the catchment outlet. 63 A **max_depth** of 1 means that the algorithm will search among the 64 combinations in 65 (``row - 1``, ``row``, ``row + 1``; ``col - 1``, ``col``, ``col + 1``), 66 the coordinates that minimize 67 the relative error between the given catchment area and the modeled 68 catchment area calculated from the 69 flow directions file. 70 :type `int`, default 1 71 :param area_error_th: Tolerance error during the positionning of the outlets. If the Error `(Ssim-Sobs)/Sobs > area_error_th`, the outlet will be excluded. 72 :type area_error_th: float 73 :param lacuna_threshold: Lacuna threshold for the discharges in percent. All gauge where the proportion of lacuna exceed this threshold will be removed. 74 :type: float | Nonetype 75 76 """ 77 78 if param.bbox is not None: 79 bbox = [ 80 param.bbox["left"], 81 param.bbox["right"], 82 param.bbox["bottom"], 83 param.bbox["top"], 84 ] 85 else: 86 bbox = None 87 88 if not os.path.exists(param.outlets_database): 89 param.outlets_database = os.path.join( 90 param.asset_dir, "outlets", param.outlets_database 91 ) 92 93 if os.path.exists(param.outlets_database): 94 stations_calage = pd.read_csv(param.outlets_database) 95 else: 96 raise ValueError(f"</> Error: file {param.outlets_database} not found") 97 98 # Pointeur ou copy of param.outletsID ? ici pointeur, ca veux dire que les stations enlevé du mesh sont aussi enlevé de param 99 Input_outletsID = param.outletsID 100 101 if param.outletsID is not None: 102 if len(param.outletsID) > 0: 103 stations_calage = stations_calage.loc[ 104 stations_calage[param.outlets_database_fields["id"]].isin( 105 param.outletsID 106 ) 107 ] 108 109 # check and print outlets not in database 110 for index, id_outlets in enumerate(Input_outletsID): 111 112 if ( 113 id_outlets 114 not in stations_calage[param.outlets_database_fields["id"]].values 115 ): 116 117 print( 118 f"</> Warning: Outlets `{Input_outletsID.pop(index)}` does not exist in the outlets database. This outlets will not be included in the mesh..." 119 ) 120 121 if query is not None: 122 stations_calage = stations_calage.query(query) 123 124 if bbox is not None: 125 stations_calage = stations_calage[ 126 ( 127 stations_calage[param.outlets_database_fields["coord_x"]] 128 >= param.bbox["left"] 129 ) 130 & ( 131 stations_calage[param.outlets_database_fields["coord_x"]] 132 <= param.bbox["right"] 133 ) 134 & ( 135 stations_calage[param.outlets_database_fields["coord_y"]] 136 >= param.bbox["bottom"] 137 ) 138 & ( 139 stations_calage[param.outlets_database_fields["coord_y"]] 140 <= param.bbox["top"] 141 ) 142 ] 143 144 # check and print outlets exluded outside bbox 145 for index, id_outlets in enumerate(Input_outletsID): 146 if ( 147 id_outlets 148 not in stations_calage[param.outlets_database_fields["id"]].values 149 ): 150 print( 151 f"</> Warning: Outlets `{Input_outletsID.pop(index)}` outside the boundingbox {bbox}. This outlets will not be included in the mesh..." 152 ) 153 154 if len(stations_calage) == 0: 155 print( 156 f"</> Error: outlets {param.outletsID} not found in" 157 "{param.outlets_database}" 158 ) 159 raise ValueError( 160 f"</> Error: outlets {param.outletsID} not found in" 161 "{param.outlets_database}" 162 ) 163 164 columns = { 165 "coord_x": param.outlets_database_fields["coord_x"], 166 "coord_y": param.outlets_database_fields["coord_y"], 167 "area": param.outlets_database_fields["area"], 168 "id": param.outlets_database_fields["id"], 169 } 170 171 # fist build 172 self.mesh = smash.factory.generate_mesh( 173 flwdir_path=param.flowdir, 174 bbox=bbox, 175 x=np.array(stations_calage[columns["coord_x"]][:]), 176 y=np.array(stations_calage[columns["coord_y"]][:]), 177 area=np.array( 178 stations_calage[columns["area"]][:] * 1e6 179 ), # Convert km² to m² 180 code=np.array(stations_calage[columns["id"]][:]), 181 epsg=param.epsg, 182 shp_path=param.outlets_shapefile, 183 max_depth=max_depth, 184 area_error_th=area_error_th, 185 ) 186 187 if lacuna_threshold is not None: 188 setup = copy.deepcopy(self._setup.setup) 189 setup.update( 190 { 191 "read_prcp": False, 192 "read_pet": False, 193 "read_qobs": True, 194 "adjust_interception": False, 195 "compute_mean_atmos": False, 196 } 197 ) 198 199 # filter gauge if lacuna exceed a thresholds 200 model = smash.Model(setup, self.mesh) 201 qobs = model.response_data.q 202 203 valid_gauge = model.mesh.code[ 204 np.where( 205 np.sum(qobs > 0.0, axis=1) / qobs.shape[1] * 100.0 206 >= (100.0 - lacuna_threshold) 207 ) 208 ] 209 210 unvalid_gauge = model.mesh.code[ 211 np.where( 212 np.sum(qobs < 0.0, axis=1) / qobs.shape[1] * 100.0 213 > lacuna_threshold 214 ) 215 ] 216 217 if len(unvalid_gauge) > 0: 218 print( 219 f"</> Remove gauges from the mesh where total lacuna " 220 f"between {setup['start_time']} and {setup['end_time']} " 221 f"exceed {lacuna_threshold}%: {unvalid_gauge}" 222 ) 223 224 stations_calage = stations_calage.loc[ 225 stations_calage[columns["id"]].isin(valid_gauge) 226 ] 227 228 if len(stations_calage) == 0: 229 print("</> Warnings, no outlets/gauge will be added to the mesh !") 230 231 # rebuild mesh 232 self.mesh = smash.factory.generate_mesh( 233 flwdir_path=param.flowdir, 234 bbox=bbox, 235 x=np.array(stations_calage[columns["coord_x"]][:]), 236 y=np.array(stations_calage[columns["coord_y"]][:]), 237 area=np.array( 238 stations_calage[columns["area"]][:] * 1e6 239 ), # Convert km² to m² 240 code=np.array(stations_calage[columns["id"]][:]), 241 epsg=param.epsg, 242 shp_path=param.outlets_shapefile, 243 max_depth=max_depth, 244 area_error_th=area_error_th, 245 ) 246 247 if param.outlets_shapefile is not None: 248 print("</> Outlets shapefile detected. Loading outlets ...") 249 if param.outlets_database_fields["id_shapefile"] != "None": 250 col_id = param.outlets_database_fields["id_shapefile"] 251 code_shape_file = [] 252 for i in range(len(self.mesh["code"])): 253 sta = self.mesh["code"][i] 254 code_shape_file.extend( 255 stations_calage.loc[ 256 stations_calage[columns["id"]] == sta, col_id 257 ].to_list() 258 ) 259 code_shape_file = np.array(code_shape_file) 260 else: 261 code_shape_file = self.mesh["code"] 262 263 catchment_polygon = gpd.read_file(param.outlets_shapefile) 264 self.catchment_polygon = catchment_polygon.loc[ 265 catchment_polygon.code.isin(code_shape_file) 266 ] 267 del catchment_polygon 268 269 else: 270 stations_calage = pd.DataFrame(None) 271 272 if bbox is None: 273 raise ValueError( 274 "Bbox is None. If no outlets provided, the bbox must be defined." 275 ) 276 277 self.mesh = smash.factory.generate_mesh( 278 flwdir_path=param.flowdir, bbox=bbox, epsg=param.epsg, max_depth=max_depth 279 )
Class mesh(). This class has three functions and one attributes to generate, load, write and store the mesh used by Smash.
def
write_mesh(self, filename: os.PathLike | None = None):
22 def write_mesh(self, filename: os.PathLike | None = None): 23 """ 24 Write the mesh of the Smash model unsing hdf5 format. 25 :param filename: Path to the file where to write the mesh, defaults to None 26 :type filename: os.PathLike | None, optional 27 28 """ 29 if filename is not None: 30 smash.io.save_mesh(self.mesh, path=filename) 31 else: 32 raise ValueError(f"Output filename '{filename}' is None.")
Write the mesh of the Smash model unsing hdf5 format.
Parameters
- filename: Path to the file where to write the mesh, defaults to None
def
load_mesh(self, filename: os.PathLike | None = None):
34 def load_mesh(self, filename: os.PathLike | None = None): 35 """ 36 Read a mesh for Smash stored with the hdf5 format. 37 :param filename: path to the hdf5 file 38 :type filename: TYPE 39 40 """ 41 42 if os.path.exists(filename): 43 self.mesh = smash.io.read_mesh(filename) 44 else: 45 raise ValueError(f"{filename} does not exist.")
Read a mesh for Smash stored with the hdf5 format.
Parameters
- filename: path to the hdf5 file
def
generate_mesh( self, param: smashbox.init.param.smashboxparam | None = None, query: str | None = None, max_depth: float = 1.0, area_error_th: None | float = None, lacuna_threshold: None | float = None):
47 def generate_mesh( 48 self, 49 param: smashboxparam | None = None, 50 query: str | None = None, 51 max_depth: float = 1.0, 52 area_error_th: None | float = None, 53 lacuna_threshold: None | float = None, 54 ): 55 """ 56 :param param: Class param.smashboxparam(), store main smashbox parameters 57 :type param: param.smashboxparam() 58 :param query: Any pandas dataframe query as string: '(SURF>20) & (SURF<100)'. This query 59 must be build using the field (column name) in the outlet database. 60 https://pandas.pydata.org/docs/user_guide/indexing.html#the-query-method 61 :type query: str 62 :max_depth: The maximum depth accepted by the algorithm to find the catchment outlet. 63 A **max_depth** of 1 means that the algorithm will search among the 64 combinations in 65 (``row - 1``, ``row``, ``row + 1``; ``col - 1``, ``col``, ``col + 1``), 66 the coordinates that minimize 67 the relative error between the given catchment area and the modeled 68 catchment area calculated from the 69 flow directions file. 70 :type `int`, default 1 71 :param area_error_th: Tolerance error during the positionning of the outlets. If the Error `(Ssim-Sobs)/Sobs > area_error_th`, the outlet will be excluded. 72 :type area_error_th: float 73 :param lacuna_threshold: Lacuna threshold for the discharges in percent. All gauge where the proportion of lacuna exceed this threshold will be removed. 74 :type: float | Nonetype 75 76 """ 77 78 if param.bbox is not None: 79 bbox = [ 80 param.bbox["left"], 81 param.bbox["right"], 82 param.bbox["bottom"], 83 param.bbox["top"], 84 ] 85 else: 86 bbox = None 87 88 if not os.path.exists(param.outlets_database): 89 param.outlets_database = os.path.join( 90 param.asset_dir, "outlets", param.outlets_database 91 ) 92 93 if os.path.exists(param.outlets_database): 94 stations_calage = pd.read_csv(param.outlets_database) 95 else: 96 raise ValueError(f"</> Error: file {param.outlets_database} not found") 97 98 # Pointeur ou copy of param.outletsID ? ici pointeur, ca veux dire que les stations enlevé du mesh sont aussi enlevé de param 99 Input_outletsID = param.outletsID 100 101 if param.outletsID is not None: 102 if len(param.outletsID) > 0: 103 stations_calage = stations_calage.loc[ 104 stations_calage[param.outlets_database_fields["id"]].isin( 105 param.outletsID 106 ) 107 ] 108 109 # check and print outlets not in database 110 for index, id_outlets in enumerate(Input_outletsID): 111 112 if ( 113 id_outlets 114 not in stations_calage[param.outlets_database_fields["id"]].values 115 ): 116 117 print( 118 f"</> Warning: Outlets `{Input_outletsID.pop(index)}` does not exist in the outlets database. This outlets will not be included in the mesh..." 119 ) 120 121 if query is not None: 122 stations_calage = stations_calage.query(query) 123 124 if bbox is not None: 125 stations_calage = stations_calage[ 126 ( 127 stations_calage[param.outlets_database_fields["coord_x"]] 128 >= param.bbox["left"] 129 ) 130 & ( 131 stations_calage[param.outlets_database_fields["coord_x"]] 132 <= param.bbox["right"] 133 ) 134 & ( 135 stations_calage[param.outlets_database_fields["coord_y"]] 136 >= param.bbox["bottom"] 137 ) 138 & ( 139 stations_calage[param.outlets_database_fields["coord_y"]] 140 <= param.bbox["top"] 141 ) 142 ] 143 144 # check and print outlets exluded outside bbox 145 for index, id_outlets in enumerate(Input_outletsID): 146 if ( 147 id_outlets 148 not in stations_calage[param.outlets_database_fields["id"]].values 149 ): 150 print( 151 f"</> Warning: Outlets `{Input_outletsID.pop(index)}` outside the boundingbox {bbox}. This outlets will not be included in the mesh..." 152 ) 153 154 if len(stations_calage) == 0: 155 print( 156 f"</> Error: outlets {param.outletsID} not found in" 157 "{param.outlets_database}" 158 ) 159 raise ValueError( 160 f"</> Error: outlets {param.outletsID} not found in" 161 "{param.outlets_database}" 162 ) 163 164 columns = { 165 "coord_x": param.outlets_database_fields["coord_x"], 166 "coord_y": param.outlets_database_fields["coord_y"], 167 "area": param.outlets_database_fields["area"], 168 "id": param.outlets_database_fields["id"], 169 } 170 171 # fist build 172 self.mesh = smash.factory.generate_mesh( 173 flwdir_path=param.flowdir, 174 bbox=bbox, 175 x=np.array(stations_calage[columns["coord_x"]][:]), 176 y=np.array(stations_calage[columns["coord_y"]][:]), 177 area=np.array( 178 stations_calage[columns["area"]][:] * 1e6 179 ), # Convert km² to m² 180 code=np.array(stations_calage[columns["id"]][:]), 181 epsg=param.epsg, 182 shp_path=param.outlets_shapefile, 183 max_depth=max_depth, 184 area_error_th=area_error_th, 185 ) 186 187 if lacuna_threshold is not None: 188 setup = copy.deepcopy(self._setup.setup) 189 setup.update( 190 { 191 "read_prcp": False, 192 "read_pet": False, 193 "read_qobs": True, 194 "adjust_interception": False, 195 "compute_mean_atmos": False, 196 } 197 ) 198 199 # filter gauge if lacuna exceed a thresholds 200 model = smash.Model(setup, self.mesh) 201 qobs = model.response_data.q 202 203 valid_gauge = model.mesh.code[ 204 np.where( 205 np.sum(qobs > 0.0, axis=1) / qobs.shape[1] * 100.0 206 >= (100.0 - lacuna_threshold) 207 ) 208 ] 209 210 unvalid_gauge = model.mesh.code[ 211 np.where( 212 np.sum(qobs < 0.0, axis=1) / qobs.shape[1] * 100.0 213 > lacuna_threshold 214 ) 215 ] 216 217 if len(unvalid_gauge) > 0: 218 print( 219 f"</> Remove gauges from the mesh where total lacuna " 220 f"between {setup['start_time']} and {setup['end_time']} " 221 f"exceed {lacuna_threshold}%: {unvalid_gauge}" 222 ) 223 224 stations_calage = stations_calage.loc[ 225 stations_calage[columns["id"]].isin(valid_gauge) 226 ] 227 228 if len(stations_calage) == 0: 229 print("</> Warnings, no outlets/gauge will be added to the mesh !") 230 231 # rebuild mesh 232 self.mesh = smash.factory.generate_mesh( 233 flwdir_path=param.flowdir, 234 bbox=bbox, 235 x=np.array(stations_calage[columns["coord_x"]][:]), 236 y=np.array(stations_calage[columns["coord_y"]][:]), 237 area=np.array( 238 stations_calage[columns["area"]][:] * 1e6 239 ), # Convert km² to m² 240 code=np.array(stations_calage[columns["id"]][:]), 241 epsg=param.epsg, 242 shp_path=param.outlets_shapefile, 243 max_depth=max_depth, 244 area_error_th=area_error_th, 245 ) 246 247 if param.outlets_shapefile is not None: 248 print("</> Outlets shapefile detected. Loading outlets ...") 249 if param.outlets_database_fields["id_shapefile"] != "None": 250 col_id = param.outlets_database_fields["id_shapefile"] 251 code_shape_file = [] 252 for i in range(len(self.mesh["code"])): 253 sta = self.mesh["code"][i] 254 code_shape_file.extend( 255 stations_calage.loc[ 256 stations_calage[columns["id"]] == sta, col_id 257 ].to_list() 258 ) 259 code_shape_file = np.array(code_shape_file) 260 else: 261 code_shape_file = self.mesh["code"] 262 263 catchment_polygon = gpd.read_file(param.outlets_shapefile) 264 self.catchment_polygon = catchment_polygon.loc[ 265 catchment_polygon.code.isin(code_shape_file) 266 ] 267 del catchment_polygon 268 269 else: 270 stations_calage = pd.DataFrame(None) 271 272 if bbox is None: 273 raise ValueError( 274 "Bbox is None. If no outlets provided, the bbox must be defined." 275 ) 276 277 self.mesh = smash.factory.generate_mesh( 278 flwdir_path=param.flowdir, bbox=bbox, epsg=param.epsg, max_depth=max_depth 279 )
Parameters
- param: Class param.smashboxparam(), store main smashbox parameters
- query: Any pandas dataframe query as string: '(SURF>20) & (SURF<100)'. This query
must be build using the field (column name) in the outlet database.
https://pandas.pydata.org/docs/user_guide/indexing.html#the-query-method
:max_depth: The maximum depth accepted by the algorithm to find the catchment outlet.
A *max_depth* of 1 means that the algorithm will search among the
combinations in
(
row - 1,row,row + 1;col - 1,col,col + 1), the coordinates that minimize the relative error between the given catchment area and the modeled catchment area calculated from the flow directions file. :typeint, default 1 - area_error_th: Tolerance error during the positionning of the outlets. If the Error
(Ssim-Sobs)/Sobs > area_error_th, the outlet will be excluded. - lacuna_threshold: Lacuna threshold for the discharges in percent. All gauge where the proportion of lacuna exceed this threshold will be removed.