fimserve 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fimserve/.DS_Store ADDED
Binary file
@@ -0,0 +1,7 @@
1
+ import warnings
2
+
3
+ warnings.simplefilter("ignore")
4
+
5
+ from .xycoord import subsetFIM
6
+
7
+ __all__ = ["subsetFIM"]
@@ -0,0 +1,61 @@
1
+ import os
2
+ import glob
3
+ import rasterio
4
+ from rasterio.mask import mask
5
+ from pathlib import Path
6
+ import geopandas as gpd
7
+
8
+ from ..datadownload import setup_directories
9
+
10
+
11
+ def checkSHP(input_file):
12
+ if isinstance(input_file, Path):
13
+ input_file = str(input_file)
14
+
15
+ if input_file.endswith(".shp"):
16
+ gdf = gpd.read_file(input_file)
17
+ elif input_file.endswith(".gpkg"):
18
+ gdf = gpd.read_file(input_file)
19
+ elif input_file.endswith(".geojson"):
20
+ gdf = gpd.read_file(input_file)
21
+ elif input_file.endswith(".kml"):
22
+ gdf = gpd.read_file(input_file)
23
+ else:
24
+ raise ValueError(
25
+ "Unsupported file format. Please provide a shapefile GeoPackage, GeoJSON, or KML."
26
+ )
27
+ return gdf
28
+
29
+
30
+ def clipFIMforboundary(inundation_raster, shapefile_geom, output_directory, huc):
31
+ with rasterio.open(inundation_raster) as src:
32
+ geometries = shapefile_geom.geometry.tolist()
33
+ if src.crs != shapefile_geom.crs:
34
+ print("CRS mismatch. Reprojecting geometry to match raster.")
35
+ geometries = [geom.to_crs(src.crs) for geom in geometries]
36
+
37
+ out_image, out_transform = mask(src, geometries, crop=True)
38
+ out_meta = src.meta.copy()
39
+ out_meta.update(
40
+ {
41
+ "driver": "GTiff",
42
+ "count": 1,
43
+ "crs": src.crs,
44
+ "transform": out_transform,
45
+ "width": out_image.shape[2],
46
+ "height": out_image.shape[1],
47
+ }
48
+ )
49
+
50
+ output_directory = os.path.join(output_directory, "subsetFIM")
51
+ if not os.path.exists(output_directory):
52
+ os.makedirs(output_directory)
53
+
54
+ file_basename = os.path.basename(inundation_raster).split("_")[0]
55
+ output_file = os.path.join(output_directory, f"{file_basename}_subsetFIM.tif")
56
+
57
+ if not os.path.exists(output_directory):
58
+ os.makedirs(output_directory)
59
+ with rasterio.open(output_file, "w", **out_meta) as dest:
60
+ dest.write(out_image)
61
+ print(f"Clipped raster saved to {output_file}")
@@ -0,0 +1,102 @@
1
+ import os
2
+ import glob
3
+ import geopandas as gpd
4
+ from shapely.geometry import Point
5
+ from pyproj import CRS
6
+ import rasterio
7
+ from rasterio.mask import mask
8
+
9
+ from ..datadownload import setup_directories
10
+ from .shpsubset import checkSHP, clipFIMforboundary
11
+
12
+
13
+ def checkifWGS(x, y):
14
+ """Check if coordinates are in WGS84 (EPSG:4326) range."""
15
+ if -180 <= x <= 180 and -90 <= y <= 90:
16
+ return True
17
+ else:
18
+ return False
19
+
20
+
21
+ def reproject_coordinates(x, y, target_crs="EPSG:5070"):
22
+ """Reproject coordinates to target CRS."""
23
+ if checkifWGS(x, y):
24
+ from pyproj import CRS, Transformer
25
+
26
+ crs_wgs84 = CRS("EPSG:4326")
27
+ crs_target = CRS(target_crs)
28
+ transformer = Transformer.from_crs(crs_wgs84, crs_target, always_xy=True)
29
+ x_new, y_new = transformer.transform(x, y)
30
+ return x_new, y_new
31
+ else:
32
+ return x, y
33
+
34
+
35
+ def clipFIM(inundation_raster, shapefile_geom, output_directory, huc):
36
+ with rasterio.open(inundation_raster) as src:
37
+ out_image, out_transform = mask(src, [shapefile_geom], crop=True)
38
+ out_meta = src.meta.copy()
39
+ out_meta.update(
40
+ {
41
+ "driver": "GTiff",
42
+ "count": 1,
43
+ "crs": src.crs,
44
+ "transform": out_transform,
45
+ "width": out_image.shape[2],
46
+ "height": out_image.shape[1],
47
+ }
48
+ )
49
+ file_basename = os.path.basename(inundation_raster).split("_")[0]
50
+ output_file = os.path.join(output_directory, f"{file_basename}_subsetFIM.tif")
51
+
52
+ if not os.path.exists(output_directory):
53
+ os.makedirs(output_directory)
54
+
55
+ with rasterio.open(output_file, "w", **out_meta) as dest:
56
+ dest.write(out_image)
57
+
58
+ print(f"Clipped raster saved to {output_file}")
59
+ return out_image, out_transform
60
+
61
+
62
+ def withininWatershed(x, y, geopackage_path, inundation_raster, huc, output_directory):
63
+ point = Point(x, y)
64
+ watersheds = gpd.read_file(geopackage_path)
65
+
66
+ # Watershed containing the point
67
+ watershed_containing_point = None
68
+ for _, watershed in watersheds.iterrows():
69
+ if watershed["geometry"].contains(point):
70
+ watershed_containing_point = watershed
71
+ break
72
+ output_directory = os.path.join(output_directory, "subsetFIM")
73
+ if not os.path.exists(output_directory):
74
+ os.makedirs(output_directory)
75
+
76
+ if watershed_containing_point is None:
77
+ raise ValueError(f"Point is not within any watershed boundary for {huc}")
78
+ return clipFIM(
79
+ inundation_raster, watershed_containing_point["geometry"], output_directory, huc
80
+ )
81
+
82
+
83
+ def subsetFIM(location, huc, method):
84
+ code_dir, data_dir, output_dir = setup_directories()
85
+ gpkg_path = os.path.join(
86
+ output_dir, f"flood_{huc}", huc, "nwm_catchments_proj_subset.gpkg"
87
+ )
88
+ out_dir = os.path.join(output_dir, f"flood_{huc}", f"{huc}_inundation")
89
+ inundation_rasters = os.path.join(out_dir, "*_inundation.tif")
90
+ print(inundation_rasters)
91
+ files = glob.glob(inundation_rasters)
92
+ if method == "xy":
93
+ x, y = location
94
+ print("point", x, y)
95
+ x, y = reproject_coordinates(x, y)
96
+ for file in files:
97
+ print(f"Clipping {file} to watershed containing point {x}, {y}")
98
+ withininWatershed(x, y, gpkg_path, file, huc, out_dir)
99
+ elif method == "boundary":
100
+ shapefile = checkSHP(location)
101
+ for file in files:
102
+ clipFIMforboundary(file, shapefile, out_dir, huc)
fimserve/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ import warnings
2
+
3
+ warnings.simplefilter("ignore")
4
+
5
+ from .datadownload import DownloadHUC8
6
+ from .streamflowdata.nwmretrospective import getNWMretrospectivedata
7
+ from .runFIM import runOWPHANDFIM
8
+
9
+ from .streamflowdata.forecasteddata import getNWMForecasteddata
10
+
11
+ # plots
12
+ from .plot.nwmfid import plotNWMStreamflow
13
+ from .plot.usgs import getUSGSsitedata
14
+ from .plot.comparestreamflow import CompareNWMnUSGSStreamflow
15
+ from .plot.usgs import plotUSGSStreamflow
16
+ from .plot.src import plotSRC
17
+
18
+ # For table
19
+ from .plot.usgsandfid import GetUSGSIDandCorrFID
20
+
21
+ # subsetting
22
+ from .FIMsubset.xycoord import subsetFIM
23
+
24
+ # Fim visualization
25
+ from .vizualizationFIM import vizualizeFIM
26
+
27
+
28
+ # Statistics
29
+ from .statistics.calculatestatistics import CalculateStatistics
@@ -0,0 +1,112 @@
1
+ import os
2
+ import csv
3
+ import pandas as pd
4
+ import subprocess
5
+
6
+
7
+ def setup_directories():
8
+ parent_dir = os.getcwd()
9
+ code_dir = os.path.join(parent_dir, "code", "inunation-mapping")
10
+ data_dir = os.path.join(parent_dir, "data", "inputs")
11
+ output_dir = os.path.join(parent_dir, "output")
12
+
13
+ # Create the directories if not exist
14
+ os.makedirs(code_dir, exist_ok=True)
15
+ os.makedirs(data_dir, exist_ok=True)
16
+ os.makedirs(output_dir, exist_ok=True)
17
+
18
+ return code_dir, data_dir, output_dir
19
+
20
+
21
+ def clone_repository(code_dir):
22
+ repo_path = os.path.join(code_dir)
23
+
24
+ # Check if repository folder exists and has files in it
25
+ if os.path.exists(repo_path) and os.listdir(repo_path):
26
+ print(
27
+ f"Repository already exists at {repo_path} and contains files. Skipping clone."
28
+ )
29
+ else:
30
+ # Clone the repository if it doesn't exist or is empty
31
+ repo_url = "https://github.com/NOAA-OWP/inundation-mapping.git"
32
+ subprocess.run(["git", "clone", repo_url, repo_path], check=True)
33
+ print(f"Repository cloned into: {repo_path}")
34
+
35
+
36
+ def download_data(huc_number, base_dir):
37
+ output_dir = os.path.join(base_dir, f"flood_{huc_number}", str(huc_number))
38
+
39
+ # Create the directory structure if it doesn't exist
40
+ if not os.path.exists(output_dir):
41
+ os.makedirs(output_dir)
42
+
43
+ # For the CIROH
44
+ cmd = f"aws s3 sync s3://ciroh-owp-hand-fim/hand_fim_4_5_2_11/{huc_number}/ {output_dir} --no-sign-request "
45
+
46
+ # Run the AWS CLI command
47
+ os.system(cmd)
48
+ print(f"Data for HUC {huc_number}")
49
+
50
+ # Now copying the hydrotable.csv to the outside of directory as fim_inputs.csv
51
+ hydrotable_path = os.path.join(output_dir, "branch_ids.csv")
52
+ fim_inputs_path = os.path.join(base_dir, f"flood_{huc_number}", "fim_inputs.csv")
53
+
54
+ # Read the first row from branch_ids.csv
55
+ with open(hydrotable_path, "r") as infile:
56
+ reader = csv.reader(infile)
57
+ first_row = next(reader) # Get the first row
58
+
59
+ # Write the first row to fim_inputs.csv
60
+ with open(fim_inputs_path, "w", newline="") as outfile:
61
+ writer = csv.writer(outfile)
62
+ writer.writerow(first_row)
63
+
64
+ print(
65
+ f"Copied the first row of {hydrotable_path} to {fim_inputs_path} as fim_inputs.csv."
66
+ )
67
+
68
+
69
+ def uniqueFID(hydrotable, fid_dir, stream_order=None):
70
+ hydrotable_df = pd.read_csv(hydrotable)
71
+ if stream_order:
72
+ stream_order = [str(order) for order in stream_order]
73
+ hydrotable_df["order_"] = hydrotable_df["order_"].astype(str)
74
+ hydrotable_df_filtered = hydrotable_df[
75
+ hydrotable_df["order_"].isin(stream_order)
76
+ ]
77
+ unique_FIDs = hydrotable_df_filtered["feature_id"].drop_duplicates()
78
+ else:
79
+ unique_FIDs = hydrotable_df["feature_id"].drop_duplicates()
80
+ unique_FIDs_df = pd.DataFrame(unique_FIDs, columns=["feature_id"])
81
+ unique_FIDs_df.to_csv(fid_dir, index=False)
82
+ print(f"Unique feature IDs saved to {fid_dir}.")
83
+
84
+
85
+ # IN the replacement of Docker, the environement variables are set in the .env file
86
+ def EnvFile(code_dir):
87
+ env_dir = os.path.join(code_dir, ".env")
88
+ env_content = """
89
+ inputsDir=inputs
90
+ outputsDir=output
91
+ """
92
+ os.makedirs(os.path.dirname(env_dir), exist_ok=True)
93
+
94
+ with open(env_dir, "w") as f:
95
+ f.write(env_content)
96
+
97
+
98
+ def DownloadHUC8(huc, stream_order=None):
99
+ code_dir, data_dir, output_dir = setup_directories()
100
+ clone_repository(code_dir)
101
+
102
+ # if huc is not str:
103
+ huc = str(huc)
104
+ download_data(huc, output_dir)
105
+ EnvFile(code_dir)
106
+ HUC_dir = os.path.join(output_dir, f"flood_{huc}")
107
+ hydrotable_dir = os.path.join(HUC_dir, str(huc), "hydrotable.csv")
108
+ featureID_dir = os.path.join(HUC_dir, f"feature_IDs.csv")
109
+ if stream_order is None:
110
+ uniqueFID(hydrotable_dir, featureID_dir)
111
+ else:
112
+ uniqueFID(hydrotable_dir, featureID_dir, stream_order)
@@ -0,0 +1,19 @@
1
+ import warnings
2
+
3
+ warnings.simplefilter("ignore")
4
+
5
+ from .nwmfid import plotNWMStreamflow
6
+ from .usgs import getUSGSsitedata
7
+ from .usgs import plotUSGSStreamflow
8
+ from .comparestreamflow import CompareNWMnUSGSStreamflow
9
+ from .src import plotSRC
10
+ from .usgsandfid import GetUSGSIDandCorrFID
11
+
12
+ __all__ = [
13
+ "plotNWMStreamflow",
14
+ "getUSGSsitedata",
15
+ "plotUSGSStreamflow",
16
+ "CompareNWMnUSGSStreamflow",
17
+ "plotSRC",
18
+ "GetUSGSIDandCorrFID",
19
+ ]
@@ -0,0 +1,69 @@
1
+ import os
2
+ import matplotlib.pyplot as plt
3
+
4
+ from .nwmfid import getFIDdata
5
+ from .usgs import getUSGSdata
6
+ from ..datadownload import setup_directories
7
+
8
+
9
+ def plotcomparision(
10
+ data_dir_nwm, data_dir_usgs, feature_id, usgs_site, output_dir, start_date, end_date
11
+ ):
12
+ nwm_data = getFIDdata(data_dir_nwm, feature_id, start_date, end_date)
13
+ usgs_data = getUSGSdata(data_dir_usgs, usgs_site, start_date, end_date)
14
+
15
+ plt.figure(figsize=(10, 5))
16
+ # Plot NWM data with solid line
17
+ plt.plot(
18
+ nwm_data["Date"],
19
+ nwm_data["Discharge"],
20
+ label=f"NWM Streamflow of feature ID {feature_id}",
21
+ linestyle="solid",
22
+ color="#167693",
23
+ linewidth=2,
24
+ )
25
+
26
+ # Plot USGS data with dashed line
27
+ plt.plot(
28
+ usgs_data["Date"],
29
+ usgs_data["Discharge"],
30
+ label=f"USGS Streamflow of gauged site {usgs_site}",
31
+ linestyle="dashed",
32
+ color="#BF4037",
33
+ linewidth=2,
34
+ )
35
+
36
+ plt.xlabel("Date (Hourly)", fontsize=14)
37
+ plt.ylabel("Discharge (m³/s)", fontsize=14)
38
+ plt.title(f"Discharge comparison between USGS and NWM streamflow", fontsize=16)
39
+ plt.legend()
40
+ plt.xticks(rotation=45, fontsize=12)
41
+ plt.yticks(fontsize=12)
42
+ plt.tight_layout()
43
+ # Save dir
44
+ plt_dir = os.path.join(output_dir, "Plots")
45
+ os.makedirs(plt_dir, exist_ok=True)
46
+ plot_dir = os.path.join(plt_dir, f"NWMvsUSGS_{usgs_site}.png")
47
+ plt.grid(True, which="both", linestyle="-", linewidth=0.3)
48
+ plt.savefig(plot_dir, dpi=500, bbox_inches="tight")
49
+ plt.show()
50
+
51
+
52
+ def CompareNWMnUSGSStreamflow(huc, feature_id, usgs_site, start_date, end_date):
53
+ code_dir, data_dir, output_dir = setup_directories()
54
+ discharge_dir_nwm = os.path.join(
55
+ output_dir, f"flood_{huc}", "discharge", "nwm30_retrospective"
56
+ )
57
+ discharge_dir_usgs = os.path.join(
58
+ output_dir, f"flood_{huc}", "discharge", "usgs_streamflow"
59
+ )
60
+ HUC_dir = os.path.join(output_dir, f"flood_{huc}")
61
+ plotcomparision(
62
+ discharge_dir_nwm,
63
+ discharge_dir_usgs,
64
+ feature_id,
65
+ usgs_site,
66
+ HUC_dir,
67
+ start_date,
68
+ end_date,
69
+ )
@@ -0,0 +1,123 @@
1
+ import pandas as pd
2
+ import os
3
+ import matplotlib.pyplot as plt
4
+
5
+ from ..datadownload import setup_directories
6
+
7
+
8
+ def getFIDdata(data_dir, feature_id, start_date, end_date):
9
+ location_id = f"nwm30-{feature_id}"
10
+
11
+ if isinstance(start_date, str):
12
+ start_date = pd.to_datetime(start_date)
13
+ if isinstance(end_date, str):
14
+ end_date = pd.to_datetime(end_date)
15
+
16
+ start_dateSTR = start_date.strftime("%Y%m%d")
17
+ end_dateSTR = end_date.strftime("%Y%m%d")
18
+ target_file = f"{start_dateSTR}_{end_dateSTR}.parquet"
19
+
20
+ target_dir = os.path.join(data_dir, target_file)
21
+
22
+ if not os.path.exists(target_dir):
23
+ raise FileNotFoundError(
24
+ f"No NWM data found for the date range: {target_file} in {data_dir}. "
25
+ "Please check the date range that you downloaded for that HUC."
26
+ )
27
+
28
+ df = pd.read_parquet(target_dir)
29
+ matched_rows = df[df["location_id"] == location_id]
30
+
31
+ # Prepare the filtered data with desired columns
32
+ filtered_data = matched_rows[["value_time", "value"]].copy()
33
+ filtered_data.rename(
34
+ columns={"value_time": "Date", "value": "Discharge"}, inplace=True
35
+ )
36
+
37
+ return filtered_data
38
+
39
+
40
+ # For the default feature_id
41
+ def getFeatureWithMaxDischarge(data_dir, start_date, end_date):
42
+ start_dateSTR = pd.to_datetime(start_date).strftime("%Y%m%d")
43
+ end_dateSTR = pd.to_datetime(end_date).strftime("%Y%m%d")
44
+ target_file = f"{start_dateSTR}_{end_dateSTR}.parquet"
45
+
46
+ target_dir = os.path.join(data_dir, target_file)
47
+
48
+ if not os.path.exists(target_dir):
49
+ raise FileNotFoundError(
50
+ f"No NWM data found for the date range: {target_file} in {data_dir}. "
51
+ "Please check the date range that you downloaded for that HUC."
52
+ )
53
+
54
+ df = pd.read_parquet(target_dir)
55
+ max_discharge_row = df.loc[df["value"].idxmax()]
56
+ max_feature_id = max_discharge_row["location_id"].split("-")[1]
57
+ return max_feature_id
58
+
59
+
60
+ def plotNWMStreamflowData(dischargedata, feature_ids, output_dir, start_date, end_date):
61
+ plt.figure(figsize=(10, 5))
62
+ missing_ids = []
63
+ plotted_ids = []
64
+
65
+ for feature_id in feature_ids:
66
+ try:
67
+ data = getFIDdata(dischargedata, feature_id, start_date, end_date)
68
+ if data.empty:
69
+ raise ValueError(f"No data for feature ID: {feature_id}")
70
+
71
+ # Plot data only if it exists
72
+ plt.plot(
73
+ data["Date"],
74
+ data["Discharge"],
75
+ label=f"NWM streamflow for feature ID: {feature_id}",
76
+ linewidth=2,
77
+ )
78
+ plotted_ids.append(feature_id)
79
+ except (FileNotFoundError, ValueError):
80
+ missing_ids.append(feature_id)
81
+
82
+ plt.xlabel("Date (Hourly)", fontsize=14)
83
+ plt.ylabel("Discharge (m³/s)", fontsize=14)
84
+ plt.title("NWM hourly streamflow", fontsize=16)
85
+ plt.xticks(rotation=45, fontsize=12)
86
+ plt.yticks(fontsize=12)
87
+ plt.grid(True, which="both", linestyle="-", linewidth=0.3)
88
+ plt.tight_layout()
89
+
90
+ if plotted_ids:
91
+ plt.legend()
92
+ # Save the plot
93
+ plt_dir = os.path.join(output_dir, "Plots")
94
+ os.makedirs(plt_dir, exist_ok=True)
95
+ plot_dir = os.path.join(plt_dir, "NWMStreamflow.png")
96
+ plt.savefig(plot_dir, dpi=500, bbox_inches="tight")
97
+ plt.show()
98
+ else:
99
+ print(
100
+ "\033[1m****No valid data found for any provided feature IDs. Plot not generated.****\033[0m"
101
+ )
102
+
103
+ if missing_ids:
104
+ print(
105
+ f"\033[1m****Data not found for the following NWM feature IDs: {', '.join(map(str, missing_ids))}****\033[0m"
106
+ )
107
+
108
+
109
+ # Main function to drive the process
110
+ def plotNWMStreamflow(huc, start_date, end_date, feature_ids=None):
111
+ code_dir, data_dir, output_dir = setup_directories()
112
+ huc_dir = os.path.join(output_dir, f"flood_{huc}")
113
+ discharge_dir = os.path.join(
114
+ output_dir, f"flood_{huc}", "discharge", "nwm30_retrospective"
115
+ )
116
+ if feature_ids is None or not feature_ids:
117
+ max_feature_id = getFeatureWithMaxDischarge(discharge_dir, start_date, end_date)
118
+ print(
119
+ f"*****No feature_id provided. Using the feature with max discharge: {max_feature_id}******"
120
+ )
121
+ feature_ids = [max_feature_id]
122
+
123
+ plotNWMStreamflowData(discharge_dir, feature_ids, huc_dir, start_date, end_date)
fimserve/plot/src.py ADDED
@@ -0,0 +1,93 @@
1
+ import os
2
+ import pandas as pd
3
+ import matplotlib.pyplot as plt
4
+
5
+ from ..datadownload import setup_directories
6
+
7
+
8
+ def filterhydroID(file_path, hydro_ids, branch_ids):
9
+ df = pd.read_csv(file_path)
10
+
11
+ df["HydroID"] = df["HydroID"].astype(str)
12
+ df["branch_id"] = df["branch_id"].astype(str)
13
+
14
+ filtered_dfs = []
15
+ for hydro_id, branch_id in zip(hydro_ids, branch_ids):
16
+ filtered_df = df[(df["HydroID"] == hydro_id) & (df["branch_id"] == branch_id)]
17
+ result_df = filtered_df[["stage", "default_discharge_cms"]]
18
+ filtered_dfs.append(result_df)
19
+ return filtered_dfs
20
+
21
+
22
+ def plotsrc(file, hydro_ids, branch_ids, output_dir, discharge_value=None):
23
+ data_list = filterhydroID(file, hydro_ids, branch_ids)
24
+ plt.figure(figsize=(5, 4))
25
+ cmap = plt.get_cmap("tab10" if len(data_list) <= 10 else "hsv")
26
+ colors = [cmap(i / len(data_list)) for i in range(len(data_list))]
27
+
28
+ # Fill colors for the markers
29
+ marker_fill_colors = ["pink", "lightblue", "lightgreen", "lightcoral", "yellow"]
30
+
31
+ for i, data in enumerate(data_list):
32
+ plt.plot(
33
+ data["default_discharge_cms"],
34
+ data["stage"],
35
+ label=f"Synthetic rating curve for HydroID: {hydro_ids[i]}",
36
+ linestyle="-",
37
+ linewidth=2.5,
38
+ color=colors[i],
39
+ )
40
+
41
+ if discharge_value:
42
+ stage_at_discharge = data["stage"].iloc[
43
+ (data["default_discharge_cms"] - discharge_value).abs().argmin()
44
+ ]
45
+
46
+ plt.plot(
47
+ [discharge_value, discharge_value],
48
+ [0, stage_at_discharge],
49
+ linestyle="--",
50
+ color=colors[i],
51
+ linewidth=2,
52
+ )
53
+ plt.plot(
54
+ [0, discharge_value],
55
+ [stage_at_discharge, stage_at_discharge],
56
+ linestyle="--",
57
+ color=colors[i],
58
+ linewidth=2,
59
+ )
60
+ # Marker at the intersection
61
+ plt.scatter(
62
+ discharge_value,
63
+ stage_at_discharge,
64
+ color=marker_fill_colors[i % len(marker_fill_colors)],
65
+ edgecolor="black",
66
+ s=200,
67
+ marker="*",
68
+ zorder=5,
69
+ label=f"For discharge {discharge_value} cms, stage: {stage_at_discharge:.2f} m",
70
+ )
71
+
72
+ plt.xlabel("Discharge (m³/s)", fontsize=14)
73
+ plt.ylabel("Stage (in m)", fontsize=14)
74
+ plt.title("Synthetic Rating Curves", fontsize=16)
75
+ plt.legend()
76
+ plt.xticks(fontsize=12)
77
+ plt.yticks(fontsize=12)
78
+ plt.tight_layout()
79
+ plt.grid(True, which="both", linestyle="-", linewidth=0.3)
80
+
81
+ # Save dir
82
+ plt_dir = os.path.join(output_dir, "Plots")
83
+ os.makedirs(plt_dir, exist_ok=True)
84
+ plot_dir = os.path.join(plt_dir, f"SRC_{hydro_ids[0]}.png")
85
+ plt.savefig(plot_dir, dpi=500, bbox_inches="tight")
86
+ plt.show()
87
+
88
+
89
+ def plotSRC(huc, hydro_ids, branch_ids, discharge_value=None):
90
+ code_dir, data_dir, output_dir = setup_directories()
91
+ HUC_dir = os.path.join(output_dir, f"flood_{huc}")
92
+ hydrotable_dir = os.path.join(output_dir, f"flood_{huc}", huc, "hydrotable.csv")
93
+ plotsrc(hydrotable_dir, hydro_ids, branch_ids, HUC_dir, discharge_value)