The Bank Lines module is responsible for detecting bank lines from hydrodynamic simulation results. It is one of the core components of the D-FAST Bank Erosion software.
The Bank Lines module processes hydrodynamic simulation results to detect bank lines, which are the boundaries between wet and dry areas in the river. These bank lines are then used as input for bank erosion calculations. The module can detect bank lines for multiple simulations and combine them into a single set of bank lines.
classBankLines(BaseCalculator):"""Bank line detection class."""def__init__(self,config_file:ConfigFile,gui:bool=False):"""Bank line initializer. Args: config_file : configparser.ConfigParser Analysis configuration settings. gui : bool Flag indicating whether this routine is called from the GUI. Examples: ```python >>> from unittest.mock import patch >>> from dfastbe.io.config import ConfigFile >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> bank_lines = BankLines(config_file) # doctest: +ELLIPSIS N...e >>> isinstance(bank_lines, BankLines) True ``` """super().__init__(config_file,gui)# the root_dir is used to get the FigureDir in the `_get_plotting_flags`self.bank_output_dir=config_file.get_output_dir("banklines")# set plotting flagsself.plot_flags=config_file.get_plotting_flags(self.root_dir)self.river_data=BankLinesRiverData(config_file)self.search_lines=self.river_data.search_linesself.simulation_data,self.critical_water_depth=(self.river_data.simulation_data())ifself.plot_flags.plot_data:self.plotter=self.get_plotter()@propertydefmax_river_width(self)->int:"""int: Maximum river width in meters."""returnMAX_RIVER_WIDTHdefget_plotter(self)->BankLinesPlotter:returnBankLinesPlotter(self.gui,self.plot_flags,self.config_file.crs,self.simulation_data,self.river_data.river_center_line,self.river_data.river_center_line.station_bounds,)defdetect(self)->None:"""Run the bank line detection analysis for a specified configuration. This method performs bank line detection using the provided configuration file. It generates shapefiles that can be opened with GeoPandas or QGIS, and also creates a plot of the detected bank lines along with the simulation data. Examples: ```python >>> import matplotlib >>> matplotlib.use('Agg') >>> from dfastbe.io.config import ConfigFile >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> bank_lines = BankLines(config_file) # doctest: +ELLIPSIS N...e >>> bank_lines.detect() >>> bank_lines.plot() >>> bank_lines.save() 0...- ``` In the BankDir directory specified in the .cfg, the following files are created: - "raw_detected_bankline_fragments.shp" - "bank_areas.shp" - "bankline_fragments_per_bank_area.shp" - "bankfile.shp" In the FigureDir directory specified in the .cfg, the following files are created: - "1_banklinedetection.png" """timed_logger("-- start analysis --")log_text("header_banklines",data={"version":__version__,"location":"https://github.com/Deltares/D-FAST_Bank_Erosion",},)log_text("-")# clip the chainage path to the range of chainages of interestriver_center_line=self.river_data.river_center_lineriver_center_line_values=river_center_line.valuescenter_line_arr=river_center_line.as_array()bank_areas:List[Polygon]=self.search_lines.to_polygons()to_right=[True]*self.search_lines.sizeforibinrange(self.search_lines.size):to_right[ib]=on_right_side(np.array(self.search_lines.values[ib].coords),center_line_arr[:,:2])log_text("identify_banklines")banklines=self.detect_bank_lines(self.simulation_data,self.critical_water_depth,self.config_file)# clip the set of detected bank lines to the bank areaslog_text("simplify_banklines")bank=[]masked_bank_lines=[]forib,bank_areainenumerate(bank_areas):log_text("bank_lines",data={"ib":ib+1})masked_bank_line=self.mask(banklines,bank_area)ifnotmasked_bank_line.is_empty:masked_bank_lines.append(masked_bank_line)bank.append(sort_connect_bank_lines(masked_bank_line,river_center_line_values,to_right[ib]))self.results={"bank":bank,"banklines":banklines,"masked_bank_lines":masked_bank_lines,"bank_areas":bank_areas,}log_text("end_banklines")timed_logger("-- stop analysis --")@staticmethoddefmask(banklines:GeoSeries,bank_area:Polygon)->MultiLineString:""" Clip the bank line segments to the area of interest. Args: banklines (GeoSeries): Unordered set of bank line segments. bank_area (Polygon): A search area corresponding to one of the bank search lines. Returns: MultiLineString: Un-ordered set of bank line segments, clipped to bank area. Examples: ```python >>> from dfastbe.io.config import ConfigFile >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> river_data = BankLinesRiverData(config_file) # doctest: +ELLIPSIS N...e >>> bank_lines = BankLines(config_file) N...e >>> simulation_data, critical_water_depth = river_data.simulation_data() N...e >>> banklines = bank_lines.detect_bank_lines(simulation_data, critical_water_depth, config_file) P...) >>> bank_area = bank_lines.search_lines.to_polygons()[0] >>> bank_lines.mask(banklines, bank_area) <MULTILINESTRING ((207830.389 392063.658, 2078...> ``` """# intersection returns one MultiLineString objectmasked_bank_lines=banklines.intersection(bank_area)[0]returnmasked_bank_linesdefplot(self):ifself.plot_flags.plot_data:self.plotter.plot(self.search_lines.size,self.results["bank"],self.results["bank_areas"],)defsave(self):"""Save results to files."""ifself.resultsisNone:raiseValueError("No results to save. Run the detect method first.")bank_name=self.config_file.get_str("General","BankFile","bankfile")bank_file=self.bank_output_dir/f"{bank_name}{EXTENSION}"log_text("save_banklines",data={"file":bank_file})gpd.GeoSeries(self.results["bank"],crs=self.config_file.crs).to_file(bank_file)gpd.GeoSeries(self.results["masked_bank_lines"],crs=self.config_file.crs).to_file(self.bank_output_dir/f"{BANKLINE_FRAGMENTS_PER_BANK_AREA_FILE}{EXTENSION}")self.results["banklines"].to_file(self.bank_output_dir/f"{RAW_DETECTED_BANKLINE_FRAGMENTS_FILE}{EXTENSION}")gpd.GeoSeries(self.results["bank_areas"],crs=self.config_file.crs).to_file(self.bank_output_dir/f"{BANK_AREAS_FILE}{EXTENSION}")@staticmethoddefdetect_bank_lines(simulation_data:BaseSimulationData,critical_water_depth:float,config_file:ConfigFile,)->gpd.GeoSeries:"""Detect all possible bank line segments based on simulation data. Use a critical water depth critical_water_depth as a water depth threshold for dry/wet boundary. Args: simulation_data (BaseSimulationData): Simulation data: mesh, bed levels, water levels, velocities, etc. critical_water_depth (float): Critical water depth for determining the banks. Returns: geopandas.GeoSeries: The collection of all detected bank segments in the remaining model area. Examples: ```python >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> river_data = BankLinesRiverData(config_file) # doctest: +ELLIPSIS N...e >>> simulation_data, critical_water_depth = river_data.simulation_data() N...e >>> BankLines.detect_bank_lines(simulation_data, critical_water_depth, config_file) P... 0 MULTILINESTRING ((207927.151 391960.747, 20792... dtype: geometry ``` """h_node=BankLines._calculate_water_depth(simulation_data)lines=BankLines._generate_bank_lines(simulation_data,h_node,critical_water_depth)multi_line=union_all(lines)merged_line=line_merge(multi_line)returngpd.GeoSeries(merged_line,crs=config_file.crs)@staticmethoddef_calculate_water_depth(simulation_data:BaseSimulationData,)->np.ndarray:"""Calculate the water depth at each node in the simulation data. This method computes the water depth for each node by considering the water levels at the faces and the bed elevation values at the nodes. There are two cases to consider: (1) the water levels at the "dry" faces are equal to the bed level at the faces (Delft3D-FLOW, WAQUA, D-Flow FM before June 2023) (2) the water levels at the "dry" faces are undefined (D-Flow FM after June 2023) Args: simulation_data (BaseSimulationData): Simulation data containing face-node relationships, water levels, and bed elevation values. Returns: np.ndarray: An array representing the water depth at each node. """face_node=simulation_data.face_nodemax_num_nodes=face_node.shape[1]num_nodes_total=len(simulation_data.x_node)# mask all nodes that shouldn't be used to compute the water level at the nodes.# start with all unused entries in the face node connectivity# mask all node indices if the water level at the face is undefined (dry)mask=face_node.mask.copy()mask[np.isnan(simulation_data.water_level_face)]=True# construct two arrays of equal size:# * an array of the unmasked node indices in face_node, and# * an array of water levels at the corresponding facetotal_num_node_indices=face_node.sizenum_masked_node_indices=sum(mask.reshape(total_num_node_indices))num_unmasked_node_indices=total_num_node_indices-num_masked_node_indicesunmasked=~masknode_indices=face_node[unmasked]water_levels=np.repeat(simulation_data.water_level_face,max_num_nodes)[unmasked.flatten()]# compute the water level at nodes as the average of the water levels at all corresponding faces and use the bed level if none of the corresponding faces has a water levelzw_node=np.bincount(node_indices,weights=water_levels,minlength=num_nodes_total)num_val=np.bincount(node_indices,weights=np.ones(num_unmasked_node_indices),minlength=num_nodes_total)zw_node=zw_node/np.maximum(num_val,1)zw_node[num_val==0]=simulation_data.bed_elevation_values[num_val==0]h_node=zw_node-simulation_data.bed_elevation_valuesreturnh_node@staticmethoddef_generate_bank_lines(simulation_data:BaseSimulationData,h_node:np.ndarray,critical_water_depth:float,)->List[LineString]:"""Detect bank lines based on wet/dry nodes. Args: simulation_data (BaseSimulationData): Simulation data: mesh, bed levels, water levels, velocities, etc. h_node (np.ndarray): Water depth at each node. critical_water_depth (float): Critical water depth for determining the banks. Returns: List[LineString or MultiLineString]: List of detected bank lines. """num_faces=len(simulation_data.face_node)lines=[]foriinrange(num_faces):BankLines._progress_bar(i,num_faces)n_node=simulation_data.n_nodes[i]face_nodes=simulation_data.face_node[i,:n_node]x_face_nodes=simulation_data.x_node[face_nodes]y_face_nodes=simulation_data.y_node[face_nodes]h_face_nodes=h_node[face_nodes]wet_face_nodes=h_face_nodes>critical_water_depthn_wet=wet_face_nodes.sum()ifn_wet==0orn_wet==n_node:continueifn_node==3:line=tri_to_line(x_face_nodes,y_face_nodes,wet_face_nodes,h_face_nodes,critical_water_depth)else:line=poly_to_line(n_node,x_face_nodes,y_face_nodes,wet_face_nodes,h_face_nodes,critical_water_depth,)iflineisnotNone:lines.append(line)returnlines@staticmethoddef_progress_bar(current:int,total:int)->None:"""Print progress bar. Args: current (int): Current iteration. total (int): Total iterations. """ifcurrent%100==0:percent=(current/total)*100print(f"Progress: {percent:.2f}% ({current}/{total})",end="\r")ifcurrent==total-1:print("Progress: 100.00% (100%)")
def__init__(self,config_file:ConfigFile,gui:bool=False):"""Bank line initializer. Args: config_file : configparser.ConfigParser Analysis configuration settings. gui : bool Flag indicating whether this routine is called from the GUI. Examples: ```python >>> from unittest.mock import patch >>> from dfastbe.io.config import ConfigFile >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> bank_lines = BankLines(config_file) # doctest: +ELLIPSIS N...e >>> isinstance(bank_lines, BankLines) True ``` """super().__init__(config_file,gui)# the root_dir is used to get the FigureDir in the `_get_plotting_flags`self.bank_output_dir=config_file.get_output_dir("banklines")# set plotting flagsself.plot_flags=config_file.get_plotting_flags(self.root_dir)self.river_data=BankLinesRiverData(config_file)self.search_lines=self.river_data.search_linesself.simulation_data,self.critical_water_depth=(self.river_data.simulation_data())ifself.plot_flags.plot_data:self.plotter=self.get_plotter()
Run the bank line detection analysis for a specified configuration.
This method performs bank line detection using the provided configuration file.
It generates shapefiles that can be opened with GeoPandas or QGIS, and also
creates a plot of the detected bank lines along with the simulation data.
In the BankDir directory specified in the .cfg, the following files are created:
- "raw_detected_bankline_fragments.shp"
- "bank_areas.shp"
- "bankline_fragments_per_bank_area.shp"
- "bankfile.shp"
In the FigureDir directory specified in the .cfg, the following files are created:
- "1_banklinedetection.png"
Source code in src/dfastbe/bank_lines/bank_lines.py
defdetect(self)->None:"""Run the bank line detection analysis for a specified configuration. This method performs bank line detection using the provided configuration file. It generates shapefiles that can be opened with GeoPandas or QGIS, and also creates a plot of the detected bank lines along with the simulation data. Examples: ```python >>> import matplotlib >>> matplotlib.use('Agg') >>> from dfastbe.io.config import ConfigFile >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> bank_lines = BankLines(config_file) # doctest: +ELLIPSIS N...e >>> bank_lines.detect() >>> bank_lines.plot() >>> bank_lines.save() 0...- ``` In the BankDir directory specified in the .cfg, the following files are created: - "raw_detected_bankline_fragments.shp" - "bank_areas.shp" - "bankline_fragments_per_bank_area.shp" - "bankfile.shp" In the FigureDir directory specified in the .cfg, the following files are created: - "1_banklinedetection.png" """timed_logger("-- start analysis --")log_text("header_banklines",data={"version":__version__,"location":"https://github.com/Deltares/D-FAST_Bank_Erosion",},)log_text("-")# clip the chainage path to the range of chainages of interestriver_center_line=self.river_data.river_center_lineriver_center_line_values=river_center_line.valuescenter_line_arr=river_center_line.as_array()bank_areas:List[Polygon]=self.search_lines.to_polygons()to_right=[True]*self.search_lines.sizeforibinrange(self.search_lines.size):to_right[ib]=on_right_side(np.array(self.search_lines.values[ib].coords),center_line_arr[:,:2])log_text("identify_banklines")banklines=self.detect_bank_lines(self.simulation_data,self.critical_water_depth,self.config_file)# clip the set of detected bank lines to the bank areaslog_text("simplify_banklines")bank=[]masked_bank_lines=[]forib,bank_areainenumerate(bank_areas):log_text("bank_lines",data={"ib":ib+1})masked_bank_line=self.mask(banklines,bank_area)ifnotmasked_bank_line.is_empty:masked_bank_lines.append(masked_bank_line)bank.append(sort_connect_bank_lines(masked_bank_line,river_center_line_values,to_right[ib]))self.results={"bank":bank,"banklines":banklines,"masked_bank_lines":masked_bank_lines,"bank_areas":bank_areas,}log_text("end_banklines")timed_logger("-- stop analysis --")
@staticmethoddefdetect_bank_lines(simulation_data:BaseSimulationData,critical_water_depth:float,config_file:ConfigFile,)->gpd.GeoSeries:"""Detect all possible bank line segments based on simulation data. Use a critical water depth critical_water_depth as a water depth threshold for dry/wet boundary. Args: simulation_data (BaseSimulationData): Simulation data: mesh, bed levels, water levels, velocities, etc. critical_water_depth (float): Critical water depth for determining the banks. Returns: geopandas.GeoSeries: The collection of all detected bank segments in the remaining model area. Examples: ```python >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> river_data = BankLinesRiverData(config_file) # doctest: +ELLIPSIS N...e >>> simulation_data, critical_water_depth = river_data.simulation_data() N...e >>> BankLines.detect_bank_lines(simulation_data, critical_water_depth, config_file) P... 0 MULTILINESTRING ((207927.151 391960.747, 20792... dtype: geometry ``` """h_node=BankLines._calculate_water_depth(simulation_data)lines=BankLines._generate_bank_lines(simulation_data,h_node,critical_water_depth)multi_line=union_all(lines)merged_line=line_merge(multi_line)returngpd.GeoSeries(merged_line,crs=config_file.crs)
@staticmethoddefmask(banklines:GeoSeries,bank_area:Polygon)->MultiLineString:""" Clip the bank line segments to the area of interest. Args: banklines (GeoSeries): Unordered set of bank line segments. bank_area (Polygon): A search area corresponding to one of the bank search lines. Returns: MultiLineString: Un-ordered set of bank line segments, clipped to bank area. Examples: ```python >>> from dfastbe.io.config import ConfigFile >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> river_data = BankLinesRiverData(config_file) # doctest: +ELLIPSIS N...e >>> bank_lines = BankLines(config_file) N...e >>> simulation_data, critical_water_depth = river_data.simulation_data() N...e >>> banklines = bank_lines.detect_bank_lines(simulation_data, critical_water_depth, config_file) P...) >>> bank_area = bank_lines.search_lines.to_polygons()[0] >>> bank_lines.mask(banklines, bank_area) <MULTILINESTRING ((207830.389 392063.658, 2078...> ``` """# intersection returns one MultiLineString objectmasked_bank_lines=banklines.intersection(bank_area)[0]returnmasked_bank_lines
defsave(self):"""Save results to files."""ifself.resultsisNone:raiseValueError("No results to save. Run the detect method first.")bank_name=self.config_file.get_str("General","BankFile","bankfile")bank_file=self.bank_output_dir/f"{bank_name}{EXTENSION}"log_text("save_banklines",data={"file":bank_file})gpd.GeoSeries(self.results["bank"],crs=self.config_file.crs).to_file(bank_file)gpd.GeoSeries(self.results["masked_bank_lines"],crs=self.config_file.crs).to_file(self.bank_output_dir/f"{BANKLINE_FRAGMENTS_PER_BANK_AREA_FILE}{EXTENSION}")self.results["banklines"].to_file(self.bank_output_dir/f"{RAW_DETECTED_BANKLINE_FRAGMENTS_FILE}{EXTENSION}")gpd.GeoSeries(self.results["bank_areas"],crs=self.config_file.crs).to_file(self.bank_output_dir/f"{BANK_AREAS_FILE}{EXTENSION}")
classBankLinesRiverData(BaseRiverData):@propertydefsearch_lines(self)->SearchLines:"""Get search lines for bank lines. Returns: SearchLines: Search lines for bank lines. Examples: ```python >>> from dfastbe.io.config import ConfigFile >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> bank_lines_river_data = BankLinesRiverData(config_file) No message found for read_chainage No message found for clip_chainage >>> search_lines = bank_lines_river_data.search_lines No message found for read_search_line No message found for read_search_line >>> len(search_lines.values) 2 ``` """search_lines=SearchLines(self.config_file.get_search_lines(),self.river_center_line)search_lines.d_lines=self.config_file.get_bank_search_distances(search_lines.size)returnsearch_linesdef_get_bank_lines_simulation_data(self)->Tuple[BaseSimulationData,float]:"""read simulation data and drying flooding threshold dh0 Returns: Tuple[BaseSimulationData, float]: simulation data and critical water depth (h0). """sim_file=self.config_file.get_sim_file("Detect","")log_text("read_simdata",data={"file":sim_file})simulation_data=BaseSimulationData.read(sim_file)# increase critical water depth h0 by flooding threshold dh0# get critical water depth used for defining bank line (default = 0.0 m)critical_water_depth=self.config_file.get_float("Detect","WaterDepth",default=0)h0=critical_water_depth+simulation_data.dry_wet_thresholdreturnsimulation_data,h0defsimulation_data(self)->Tuple[BaseSimulationData,float]:"""Get simulation data and critical water depth and clip to river center line. Returns: Tuple[BaseSimulationData, float]: simulation data and critical water depth (h0). Examples: ```python >>> from dfastbe.io.config import ConfigFile >>> from unittest.mock import patch >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> bank_lines_river_data = BankLinesRiverData(config_file) # doctest: +ELLIPSIS N...e >>> simulation_data, h0 = bank_lines_river_data.simulation_data() N...e >>> h0 0.1 ``` """simulation_data,h0=self._get_bank_lines_simulation_data()# clip simulation data to boundaries ...log_text("clip_data")simulation_data.clip(self.river_center_line.values,self.search_lines.max_distance)returnsimulation_data,h0
defsimulation_data(self)->Tuple[BaseSimulationData,float]:"""Get simulation data and critical water depth and clip to river center line. Returns: Tuple[BaseSimulationData, float]: simulation data and critical water depth (h0). Examples: ```python >>> from dfastbe.io.config import ConfigFile >>> from unittest.mock import patch >>> config_file = ConfigFile.read("tests/data/bank_lines/meuse_manual.cfg") >>> bank_lines_river_data = BankLinesRiverData(config_file) # doctest: +ELLIPSIS N...e >>> simulation_data, h0 = bank_lines_river_data.simulation_data() N...e >>> h0 0.1 ``` """simulation_data,h0=self._get_bank_lines_simulation_data()# clip simulation data to boundaries ...log_text("clip_data")simulation_data.clip(self.river_center_line.values,self.search_lines.max_distance)returnsimulation_data,h0
classSearchLines:def__init__(self,lines:List[LineString],mask:LineGeometry=None):"""Search lines initialization. Args: lines (List[LineString]): List of search lines. mask (LineGeometry, optional): Center line for masking the search lines. Defaults to None. """ifmaskisNone:self.values=linesself.max_distance=Noneelse:self.values,self.max_distance=self.mask(lines,mask.values)self.size=len(lines)@propertydefd_lines(self)->List[float]:ifhasattr(self,"_d_lines"):returnself._d_lineselse:raiseValueError("The d_lines property has not been set yet.")@d_lines.setterdefd_lines(self,value:List[float]):self._d_lines=value@staticmethoddefmask(search_lines:List[LineString],river_center_line:LineString,max_river_width:float=MAX_RIVER_WIDTH,)->Tuple[List[LineString],float]:""" Clip the list of lines to the envelope of a certain size surrounding a reference line. Args: search_lines (List[LineString]): List of lines to be clipped. river_center_line (LineString): Reference line to which the search lines are clipped. max_river_width: float Maximum distance away from river_profile. Returns: Tuple[List[LineString], float]: - List of clipped search lines. - Maximum distance from any point within line to reference line. Examples: ```python >>> from shapely.geometry import LineString >>> search_lines = [LineString([(0, 0), (1, 1)]), LineString([(2, 2), (3, 3)])] >>> river_center_line = LineString([(0, 0), (2, 2)]) >>> search_lines_clipped, max_distance = SearchLines.mask(search_lines, river_center_line) >>> max_distance 2.0 ``` """num=len(search_lines)profile_buffer=river_center_line.buffer(max_river_width,cap_style=2)# The algorithm uses simplified geometries for determining the distance between lines for speed.# Stay accurate to within about 1 mprofile_simplified=river_center_line.simplify(1)max_distance=0forindinrange(num):# Clip the bank search lines to the reach of interest (indicated by the reference line).search_lines[ind]=search_lines[ind].intersection(profile_buffer)# If the bank search line breaks into multiple parts, select the part closest to the reference line.ifsearch_lines[ind].geom_type=="MultiLineString":search_lines[ind]=SearchLines._select_closest_part(search_lines[ind],profile_simplified,max_river_width)# Determine the maximum distance from a point on this line to the reference line.line_simplified=search_lines[ind].simplify(1)max_distance=max([Point(c).distance(profile_simplified)forcinline_simplified.coords])# Increase the value of max_distance by 2 to account for error introduced by using simplified lines.max_distance=max(max_distance,max_distance+2)returnsearch_lines,max_distance@staticmethoddef_select_closest_part(search_lines_segments:MultiLineString,reference_line:LineString,max_river_width:float,)->LineString:"""Select the closest part of a MultiLineString to the reference line. Args: search_lines_segments (MultiLineString): The MultiLineString containing multiple line segments to evaluate. reference_line (LineString): The reference line to calculate distances. max_river_width (float): Maximum allowable distance. Returns: LineString: The closest part of the MultiLineString. """closest_part=search_lines_segments.geoms[0]min_distance=max_river_widthforpartinsearch_lines_segments.geoms:simplified_part=part.simplify(1)distance=simplified_part.distance(reference_line)ifdistance<min_distance:min_distance=distanceclosest_part=partreturnclosest_partdefto_polygons(self)->List[Polygon]:""" Construct a series of polygons surrounding the bank search lines. Returns: bank_areas: Array containing the areas of interest surrounding the bank search lines. Examples: ```python >>> search_lines = [LineString([(0, 0), (1, 1)]), LineString([(2, 2), (3, 3)])] >>> search_lines_clipped = SearchLines(search_lines) >>> search_lines_clipped.d_lines = [10, 20] >>> bank_areas = search_lines_clipped.to_polygons() >>> len(bank_areas) 2 ``` """bank_areas=[self.values[b].buffer(distance,cap_style=2)forb,distanceinenumerate(self.d_lines)]returnbank_areas
Center line for masking the search lines. Defaults to None.
None
Source code in src/dfastbe/bank_lines/data_models.py
14151617181920212223242526272829
def__init__(self,lines:List[LineString],mask:LineGeometry=None):"""Search lines initialization. Args: lines (List[LineString]): List of search lines. mask (LineGeometry, optional): Center line for masking the search lines. Defaults to None. """ifmaskisNone:self.values=linesself.max_distance=Noneelse:self.values,self.max_distance=self.mask(lines,mask.values)self.size=len(lines)
@staticmethoddefmask(search_lines:List[LineString],river_center_line:LineString,max_river_width:float=MAX_RIVER_WIDTH,)->Tuple[List[LineString],float]:""" Clip the list of lines to the envelope of a certain size surrounding a reference line. Args: search_lines (List[LineString]): List of lines to be clipped. river_center_line (LineString): Reference line to which the search lines are clipped. max_river_width: float Maximum distance away from river_profile. Returns: Tuple[List[LineString], float]: - List of clipped search lines. - Maximum distance from any point within line to reference line. Examples: ```python >>> from shapely.geometry import LineString >>> search_lines = [LineString([(0, 0), (1, 1)]), LineString([(2, 2), (3, 3)])] >>> river_center_line = LineString([(0, 0), (2, 2)]) >>> search_lines_clipped, max_distance = SearchLines.mask(search_lines, river_center_line) >>> max_distance 2.0 ``` """num=len(search_lines)profile_buffer=river_center_line.buffer(max_river_width,cap_style=2)# The algorithm uses simplified geometries for determining the distance between lines for speed.# Stay accurate to within about 1 mprofile_simplified=river_center_line.simplify(1)max_distance=0forindinrange(num):# Clip the bank search lines to the reach of interest (indicated by the reference line).search_lines[ind]=search_lines[ind].intersection(profile_buffer)# If the bank search line breaks into multiple parts, select the part closest to the reference line.ifsearch_lines[ind].geom_type=="MultiLineString":search_lines[ind]=SearchLines._select_closest_part(search_lines[ind],profile_simplified,max_river_width)# Determine the maximum distance from a point on this line to the reference line.line_simplified=search_lines[ind].simplify(1)max_distance=max([Point(c).distance(profile_simplified)forcinline_simplified.coords])# Increase the value of max_distance by 2 to account for error introduced by using simplified lines.max_distance=max(max_distance,max_distance+2)returnsearch_lines,max_distance
fromdfastbe.io.configimportConfigFilefromdfastbe.bank_lines.bank_linesimportBankLines# Load configuration fileconfig_file=ConfigFile.read("config.cfg")# Initialize BankLines objectbank_lines=BankLines(config_file)# Run bank line detectionbank_lines.detect()# plot resultsbank_lines.plot()# Save resultsbank_lines.save()
For more details on the specific methods and classes, refer to the API reference below.