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 +0 -0
- fimserve/FIMsubset/__init__.py +7 -0
- fimserve/FIMsubset/shpsubset.py +61 -0
- fimserve/FIMsubset/xycoord.py +102 -0
- fimserve/__init__.py +29 -0
- fimserve/datadownload.py +112 -0
- fimserve/plot/__init__.py +19 -0
- fimserve/plot/comparestreamflow.py +69 -0
- fimserve/plot/nwmfid.py +123 -0
- fimserve/plot/src.py +93 -0
- fimserve/plot/usgs.py +111 -0
- fimserve/plot/usgsandfid.py +35 -0
- fimserve/runFIM.py +73 -0
- fimserve/statistics/__init__.py +3 -0
- fimserve/statistics/calculatestatistics.py +134 -0
- fimserve/streamflowdata/__init__.py +8 -0
- fimserve/streamflowdata/forecasteddata.py +243 -0
- fimserve/streamflowdata/nwmretrospective.py +109 -0
- fimserve/vizualizationFIM.py +85 -0
- fimserve-0.1.6.dist-info/LICENSE.txt +661 -0
- fimserve-0.1.6.dist-info/METADATA +103 -0
- fimserve-0.1.6.dist-info/RECORD +23 -0
- fimserve-0.1.6.dist-info/WHEEL +4 -0
fimserve/.DS_Store
ADDED
|
Binary file
|
|
@@ -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
|
fimserve/datadownload.py
ADDED
|
@@ -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
|
+
)
|
fimserve/plot/nwmfid.py
ADDED
|
@@ -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)
|