loone-data-prep 1.2.2__tar.gz → 1.2.4__tar.gz

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.
Files changed (44) hide show
  1. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/PKG-INFO +3 -2
  2. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/forecast_scripts/create_forecast_LOWs.py +73 -30
  3. loone_data_prep-1.2.4/loone_data_prep/forecast_scripts/weather_forecast.py +199 -0
  4. loone_data_prep-1.2.4/loone_data_prep/herbie_utils.py +29 -0
  5. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep.egg-info/PKG-INFO +3 -2
  6. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep.egg-info/SOURCES.txt +1 -0
  7. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep.egg-info/requires.txt +2 -1
  8. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/pyproject.toml +3 -2
  9. loone_data_prep-1.2.2/loone_data_prep/forecast_scripts/weather_forecast.py +0 -155
  10. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/LICENSE +0 -0
  11. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/README.md +0 -0
  12. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/GEOGLOWS_LOONE_DATA_PREP.py +0 -0
  13. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/LOONE_DATA_PREP.py +0 -0
  14. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/__init__.py +0 -0
  15. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/data_analyses_fns.py +0 -0
  16. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/flow_data/S65E_total.py +0 -0
  17. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/flow_data/__init__.py +0 -0
  18. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/flow_data/forecast_bias_correction.py +0 -0
  19. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/flow_data/get_forecast_flows.py +0 -0
  20. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/flow_data/get_inflows.py +0 -0
  21. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/flow_data/get_outflows.py +0 -0
  22. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/flow_data/hydro.py +0 -0
  23. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/forecast_scripts/Chla_merged.py +0 -0
  24. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/forecast_scripts/forecast_stages.py +0 -0
  25. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/forecast_scripts/get_Chla_predicted.py +0 -0
  26. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/forecast_scripts/get_NO_Loads_predicted.py +0 -0
  27. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/forecast_scripts/loone_q_predict.py +0 -0
  28. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/forecast_scripts/loone_wq_predict.py +0 -0
  29. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/forecast_scripts/predict_PI.py +0 -0
  30. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/forecast_scripts/trib_cond.py +0 -0
  31. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/utils.py +0 -0
  32. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/water_level_data/__init__.py +0 -0
  33. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/water_level_data/get_all.py +0 -0
  34. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/water_level_data/hydro.py +0 -0
  35. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/water_quality_data/__init__.py +0 -0
  36. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/water_quality_data/get_inflows.py +0 -0
  37. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/water_quality_data/get_lake_wq.py +0 -0
  38. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/water_quality_data/wq.py +0 -0
  39. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/weather_data/__init__.py +0 -0
  40. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/weather_data/get_all.py +0 -0
  41. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep/weather_data/weather.py +0 -0
  42. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep.egg-info/dependency_links.txt +0 -0
  43. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/loone_data_prep.egg-info/top_level.txt +0 -0
  44. {loone_data_prep-1.2.2 → loone_data_prep-1.2.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loone_data_prep
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: Prepare data to run the LOONE model.
5
5
  Author-email: Osama Tarabih <osamatarabih@usf.edu>
6
6
  Maintainer-email: Michael Souffront <msouffront@aquaveo.com>, James Dolinar <jdolinar@aquaveo.com>
@@ -28,7 +28,8 @@ Requires-Dist: herbie-data[extras]==2025.5.0
28
28
  Requires-Dist: openmeteo_requests
29
29
  Requires-Dist: requests_cache
30
30
  Requires-Dist: retry-requests
31
- Requires-Dist: eccodes==2.42.0
31
+ Requires-Dist: eccodes==2.41.0
32
+ Requires-Dist: xarray==2025.4.0
32
33
  Dynamic: license-file
33
34
 
34
35
  LOONE_DATA_PREP
@@ -2,8 +2,11 @@ import os
2
2
  from herbie import FastHerbie
3
3
  from datetime import datetime
4
4
  import pandas as pd
5
- from retry_requests import retry
5
+ from retry_requests import retry as retry_requests
6
+ from retry import retry
6
7
  import warnings
8
+ from typing import Tuple
9
+ from loone_data_prep.herbie_utils import get_fast_herbie_object
7
10
 
8
11
 
9
12
  def generate_wind_forecasts(output_dir):
@@ -26,7 +29,8 @@ def generate_wind_forecasts(output_dir):
26
29
  }
27
30
 
28
31
  today_str = datetime.today().strftime('%Y-%m-%d 00:00')
29
- FH = FastHerbie([today_str], model="ifs", fxx=range(0, 360, 3))
32
+ FH = get_fast_herbie_object(today_str)
33
+ print("FastHerbie initialized.")
30
34
  dfs = []
31
35
 
32
36
  variables = {
@@ -45,37 +49,20 @@ def generate_wind_forecasts(output_dir):
45
49
  "latitude": [point.latitude]
46
50
  })
47
51
 
52
+ # Loop through variables for current point and extract data
48
53
  for var_key, var_name in variables.items():
54
+ # Get the current variable data at the current point
49
55
  print(f" Variable: {var_key}")
56
+ try:
57
+ df, var_name_actual = _download_herbie_variable(FH, var_key, var_name, point_df)
58
+ except Exception as e:
59
+ print(f"Error processing {var_key} for Point {index + 1} ({point.latitude}, {point.longitude}): {e}")
60
+ print(f'Skipping {var_key}')
61
+ continue
50
62
 
51
- # Download and load dataset
52
- FH.download(f":{var_key}")
53
- ds = FH.xarray(f":{var_key}", backend_kwargs={"decode_timedelta": True})
54
-
55
- # Extract point data
56
- dsi = ds.herbie.pick_points(point_df, method="nearest")
57
-
58
- # Get actual variable name
59
- if var_name == "10u":
60
- var_name_actual = "u10" # Map 10u to u10
61
- elif var_name == "10v":
62
- var_name_actual = "v10" # Map 10v to v10
63
- elif var_name == "2t":
64
- var_name_actual = "t2m" #TODO: check that this is correct
65
-
66
- # Convert to DataFrame
67
- time_series = dsi[var_name_actual].squeeze()
68
- df = time_series.to_dataframe().reset_index()
69
-
70
- # Handle datetime columns
71
- if "valid_time" in df.columns:
72
- df = df.rename(columns={"valid_time": "datetime"})
73
- elif "step" in df.columns and "time" in dsi.coords:
74
- df["datetime"] = dsi.time.values[0] + df["step"]
75
-
76
- # Retain necessary columns
77
- df = df[["datetime", var_name_actual]].drop_duplicates()
78
- dfs.append((index, var_name_actual, df))
63
+ # Append the DataFrame and variable name to the list
64
+ if not df.empty:
65
+ dfs.append((index, var_name_actual, df))
79
66
 
80
67
  # Merge and process data per point
81
68
  results = {}
@@ -125,3 +112,59 @@ def generate_wind_forecasts(output_dir):
125
112
  filepath = os.path.join(output_dir, airt_file_map[key])
126
113
  df_airt.to_csv(filepath, index=False)
127
114
 
115
+
116
+ @retry(Exception, tries=5, delay=15, max_delay=60, backoff=2)
117
+ def _download_herbie_variable(fast_herbie_object: FastHerbie, variable_key: str, variable_name: str, point_df: pd.DataFrame) -> Tuple[pd.DataFrame, str]:
118
+ """
119
+ Download a specific variable from the Herbie API.
120
+
121
+ Args:
122
+ fast_herbie_object: An instance of the FastHerbie class.
123
+ variable_key: The key of the variable to download.
124
+ variable_name: The name of the variable to download.
125
+ point_df: A DataFrame containing the point of interest (longitude and latitude).
126
+
127
+ Returns:
128
+ A DataFrame containing the downloaded variable data.
129
+
130
+ Example:
131
+ point_df = pd.DataFrame({"longitude": [-80.7934], "latitude": [27.1389]})
132
+ df, var_name_actual = _download_herbie_variable(FastHerbie('2020-05-16 00:00', model='ifs', fxx=range(0, 360, 3)), '10u', '10u', point_df)
133
+ """
134
+ # Download and load dataset
135
+ fast_herbie_object.download(f":{variable_key}")
136
+ ds = fast_herbie_object.xarray(f":{variable_key}", backend_kwargs={"decode_timedelta": True})
137
+
138
+ # Extract point data
139
+ dsi = ds.herbie.pick_points(point_df, method="nearest")
140
+
141
+ # Close and delete the original dataset to free up resources
142
+ ds.close()
143
+ del ds
144
+
145
+ # Get actual variable name
146
+ if variable_name == "10u":
147
+ var_name_actual = "u10" # Map 10u to u10
148
+ elif variable_name == "10v":
149
+ var_name_actual = "v10" # Map 10v to v10
150
+ elif variable_name == "2t":
151
+ var_name_actual = "t2m" #TODO: check that this is correct
152
+
153
+ # Convert to DataFrame
154
+ time_series = dsi[var_name_actual].squeeze()
155
+ df = time_series.to_dataframe().reset_index()
156
+
157
+ # Handle datetime columns
158
+ if "valid_time" in df.columns:
159
+ df = df.rename(columns={"valid_time": "datetime"})
160
+ elif "step" in df.columns and "time" in dsi.coords:
161
+ df["datetime"] = dsi.time.values[0] + df["step"]
162
+
163
+ # Close and delete the intermediate dataset to free memory
164
+ dsi.close()
165
+ del dsi, time_series
166
+
167
+ # Retain necessary columns
168
+ df = df[["datetime", var_name_actual]].drop_duplicates()
169
+
170
+ return df, var_name_actual
@@ -0,0 +1,199 @@
1
+ from herbie import FastHerbie
2
+ from datetime import datetime
3
+ import pandas as pd
4
+ import openmeteo_requests
5
+ import argparse
6
+ import requests_cache
7
+ from retry_requests import retry as retry_requests
8
+ from retry import retry
9
+ import warnings
10
+ from loone_data_prep.herbie_utils import get_fast_herbie_object
11
+
12
+ warnings.filterwarnings("ignore", message="Will not remove GRIB file because it previously existed.")
13
+
14
+
15
+ def download_weather_forecast(file_path):
16
+ # Get today's date in the required format
17
+ today_str = datetime.today().strftime('%Y-%m-%d 00:00')
18
+
19
+ # Define variables to download and extract
20
+ variables = {
21
+ "10u": "10u",
22
+ "ssrd": "ssrd",
23
+ "tp": "tp",
24
+ "10v": "10v",
25
+ }
26
+
27
+ # Initialize FastHerbie
28
+ FH = get_fast_herbie_object(today_str)
29
+ print("FastHerbie initialized.")
30
+
31
+ dfs = []
32
+
33
+ for var_key, var_name in variables.items():
34
+ # Download the current variable
35
+ print(f"Processing {var_key}...")
36
+ try:
37
+ df = _download_herbie_variable(FH, var_key, var_name)
38
+ except Exception as e:
39
+ print(f"Error processing {var_key}: {e}")
40
+ print(f'Skipping {var_key}')
41
+ continue
42
+
43
+ # Append to list
44
+ if not df.empty:
45
+ dfs.append(df)
46
+
47
+ try:
48
+ # Merge all variables into a single DataFrame
49
+ final_df = dfs[0]
50
+ for df in dfs[1:]:
51
+ final_df = final_df.merge(df, on="datetime", how="outer")
52
+ print(final_df)
53
+ # Calculate wind speed
54
+ final_df["wind_speed"] = (final_df["u10"] ** 2 + final_df["v10"] ** 2) ** 0.5
55
+
56
+ #rainfall corrected: OLS Regression Equation: Corrected Forecast = 0.7247 * Forecast + 0.1853
57
+ final_df["tp_corrected"] = 0.7247 * final_df["tp"] + 0.1853
58
+
59
+ #wind speed correction: Corrected Forecast = 0.4167 * Forecast + 4.1868
60
+ final_df["wind_speed_corrected"] = 0.4167 * final_df["wind_speed"] + 4.1868
61
+
62
+ #radiation correction will need to be fixed because it was done on fdir instead of ssdr
63
+ #radiation corrected: Corrected Forecast = 0.0553 * Forecast - 0.0081
64
+ final_df["ssrd_corrected"] = 0.0553 * final_df["ssrd"] - 0.0081
65
+ except Exception as e:
66
+ print(f'Error correcting herbie weather data: {e}')
67
+
68
+ try:
69
+ # Setup the Open-Meteo API client with cache and retry on error
70
+ cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
71
+ retry_session = retry_requests(cache_session, retries = 5, backoff_factor = 0.2)
72
+ openmeteo = openmeteo_requests.Client(session = retry_session)
73
+
74
+ # Make sure all required weather variables are listed here
75
+ # The order of variables in hourly or daily is important to assign them correctly below
76
+ url = "https://api.open-meteo.com/v1/forecast"
77
+ params = {
78
+ "latitude": 26.9690,
79
+ "longitude": -80.7976,
80
+ "hourly": "evapotranspiration",
81
+ "forecast_days": 16,
82
+ "models": "gfs_seamless"
83
+ }
84
+ responses = openmeteo.weather_api(url, params=params)
85
+
86
+
87
+ # Process first location. Add a for-loop for multiple locations or weather models
88
+ response = responses[0]
89
+
90
+ hourly = response.Hourly()
91
+ hourly_evapotranspiration = hourly.Variables(0).ValuesAsNumpy()
92
+
93
+ hourly_data = {"date": pd.date_range(
94
+ start = pd.to_datetime(hourly.Time(), unit = "s", utc = True),
95
+ end = pd.to_datetime(hourly.TimeEnd(), unit = "s", utc = True),
96
+ freq = pd.Timedelta(seconds = hourly.Interval()),
97
+ inclusive = "left"
98
+ )}
99
+
100
+ hourly_data["evapotranspiration"] = hourly_evapotranspiration
101
+
102
+ hourly_dataframe = pd.DataFrame(data = hourly_data)
103
+
104
+ # Convert datetime to date for merging
105
+ final_df['date'] = final_df['datetime']
106
+ # Ensure final_df['date'] is timezone-aware (convert to UTC)
107
+ final_df['date'] = pd.to_datetime(final_df['date'], utc=True)
108
+
109
+ # Ensure hourly_dataframe['date'] is also timezone-aware (convert to UTC)
110
+ hourly_dataframe['date'] = pd.to_datetime(hourly_dataframe['date'], utc=True)
111
+
112
+ # Merge while keeping only matching dates from final_df
113
+ merged_df = final_df.merge(hourly_dataframe, on='date', how='left')
114
+
115
+ # Print final combined DataFrame
116
+ merged_df.drop(columns=['date'], inplace=True)
117
+ # print(merged_df)
118
+
119
+ merged_df.to_csv(file_path, index=False)
120
+ except Exception as e:
121
+ print(f'Error retrieving openmeteo weather data: {e}')
122
+
123
+
124
+ @retry(Exception, tries=5, delay=15, max_delay=60, backoff=2)
125
+ def _download_herbie_variable(fast_herbie_object: FastHerbie, variable_key: str, variable_name: str) -> pd.DataFrame:
126
+ """
127
+ Download a specific variable from the Herbie API.
128
+
129
+ Args:
130
+ fast_herbie_object: An instance of the FastHerbie class.
131
+ variable_key: The key of the variable to download.
132
+ variable_name: The name of the variable to download.
133
+
134
+ Returns:
135
+ A DataFrame containing the downloaded variable data.
136
+
137
+ Example:
138
+ df = _download_herbie_variable(FastHerbie('2020-05-16 00:00', model='ifs', fxx=range(0, 360, 3)), '10u', '10u')
139
+ """
140
+ # Define point of interest
141
+ points = pd.DataFrame({"longitude": [-80.7976], "latitude": [26.9690]})
142
+
143
+ # Download and load the dataset
144
+ fast_herbie_object.download(f":{variable_key}")
145
+ ds = fast_herbie_object.xarray(f":{variable_key}", backend_kwargs={"decode_timedelta": True})
146
+
147
+ # Extract point data
148
+ dsi = ds.herbie.pick_points(points, method="nearest")
149
+
150
+ # Close and delete the original dataset to free up resources
151
+ ds.close()
152
+ del ds
153
+
154
+ # Extract the correct variable name dynamically
155
+ if variable_name == "10u":
156
+ var_name_actual = "u10" # Map 10u to u10
157
+ elif variable_name == "10v":
158
+ var_name_actual = "v10" # Map 10v to v10
159
+ else:
160
+ var_name_actual = variable_name # For ssrd and tp, use the same name
161
+
162
+ # Extract time series
163
+ time_series = dsi[var_name_actual].squeeze()
164
+
165
+ # Convert to DataFrame
166
+ df = time_series.to_dataframe().reset_index()
167
+
168
+ # Convert `valid_time` to datetime
169
+ if "valid_time" in df.columns:
170
+ df = df.rename(columns={"valid_time": "datetime"})
171
+ elif "step" in df.columns and "time" in dsi.coords:
172
+ df["datetime"] = dsi.time.values[0] + df["step"]
173
+
174
+ # Keep only datetime and variable of interest
175
+ df = df[["datetime", var_name_actual]].drop_duplicates()
176
+
177
+ # Print extracted data
178
+ # print(df)
179
+
180
+ # Clean up intermediate datasets to free memory
181
+ del dsi, time_series
182
+
183
+ return df
184
+
185
+
186
+ def main():
187
+ # Set up command-line argument parsing
188
+ parser = argparse.ArgumentParser(description="Download and process weather forecast data.")
189
+ parser.add_argument("file_path", help="Path to save the resulting CSV file.")
190
+
191
+ # Parse the arguments
192
+ args = parser.parse_args()
193
+
194
+ # Call the function with the provided file path
195
+ download_weather_forecast(args.file_path)
196
+
197
+
198
+ if __name__ == "__main__":
199
+ main()
@@ -0,0 +1,29 @@
1
+ from retry import retry
2
+ from herbie import FastHerbie
3
+
4
+
5
+ class NoGribFilesFoundError(Exception):
6
+ """Raised when no GRIB files are found for the specified date/model run."""
7
+ pass
8
+
9
+
10
+ @retry(NoGribFilesFoundError, tries=5, delay=15, max_delay=60, backoff=2)
11
+ def get_fast_herbie_object(date: str) -> FastHerbie:
12
+ """
13
+ Get a FastHerbie object for the specified date. Raises an exception when no GRIB files are found.
14
+
15
+ Args:
16
+ date: pandas-parsable datetime string
17
+
18
+ Returns:
19
+ A FastHerbie object configured for the specified date.
20
+
21
+ Raises:
22
+ NoGribFilesFoundError: If no GRIB files are found for the specified date.
23
+ """
24
+ fast_herbie = FastHerbie([date], model="ifs", fxx=range(0, 360, 3))
25
+
26
+ if len(fast_herbie.file_exists) == 0:
27
+ raise NoGribFilesFoundError(f"No GRIB files found for the specified date {date}.")
28
+
29
+ return fast_herbie
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loone_data_prep
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: Prepare data to run the LOONE model.
5
5
  Author-email: Osama Tarabih <osamatarabih@usf.edu>
6
6
  Maintainer-email: Michael Souffront <msouffront@aquaveo.com>, James Dolinar <jdolinar@aquaveo.com>
@@ -28,7 +28,8 @@ Requires-Dist: herbie-data[extras]==2025.5.0
28
28
  Requires-Dist: openmeteo_requests
29
29
  Requires-Dist: requests_cache
30
30
  Requires-Dist: retry-requests
31
- Requires-Dist: eccodes==2.42.0
31
+ Requires-Dist: eccodes==2.41.0
32
+ Requires-Dist: xarray==2025.4.0
32
33
  Dynamic: license-file
33
34
 
34
35
  LOONE_DATA_PREP
@@ -5,6 +5,7 @@ loone_data_prep/GEOGLOWS_LOONE_DATA_PREP.py
5
5
  loone_data_prep/LOONE_DATA_PREP.py
6
6
  loone_data_prep/__init__.py
7
7
  loone_data_prep/data_analyses_fns.py
8
+ loone_data_prep/herbie_utils.py
8
9
  loone_data_prep/utils.py
9
10
  loone_data_prep.egg-info/PKG-INFO
10
11
  loone_data_prep.egg-info/SOURCES.txt
@@ -8,4 +8,5 @@ herbie-data[extras]==2025.5.0
8
8
  openmeteo_requests
9
9
  requests_cache
10
10
  retry-requests
11
- eccodes==2.42.0
11
+ eccodes==2.41.0
12
+ xarray==2025.4.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "loone_data_prep"
7
- version = "1.2.2"
7
+ version = "1.2.4"
8
8
  description = "Prepare data to run the LOONE model."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -24,5 +24,6 @@ dependencies = [
24
24
  "openmeteo_requests",
25
25
  "requests_cache",
26
26
  "retry-requests",
27
- "eccodes==2.42.0"
27
+ "eccodes==2.41.0",
28
+ "xarray==2025.4.0"
28
29
  ]
@@ -1,155 +0,0 @@
1
- from herbie import FastHerbie
2
- from datetime import datetime
3
- import pandas as pd
4
- import openmeteo_requests
5
- import argparse
6
- import requests_cache
7
- from retry_requests import retry
8
- import warnings
9
-
10
- warnings.filterwarnings("ignore", message="Will not remove GRIB file because it previously existed.")
11
-
12
-
13
- def download_weather_forecast (file_path):
14
- # Get today's date in the required format
15
- today_str = datetime.today().strftime('%Y-%m-%d 00:00')
16
-
17
- # Define variables to download and extract
18
- variables = {
19
- "10u": "10u",
20
- "ssrd": "ssrd",
21
- "tp": "tp",
22
- "10v": "10v",
23
- }
24
-
25
- # Define point of interest
26
- points = pd.DataFrame({"longitude": [-80.7976], "latitude": [26.9690]})
27
-
28
- # Initialize FastHerbie
29
- FH = FastHerbie([today_str], model="ifs", fxx=range(0, 360, 3))
30
- dfs = []
31
-
32
- for var_key, var_name in variables.items():
33
- print(f"Processing {var_key}...")
34
-
35
- # Download and load the dataset
36
- FH.download(f":{var_key}")
37
- ds = FH.xarray(f":{var_key}", backend_kwargs={"decode_timedelta": True})
38
-
39
- # Extract point data
40
- dsi = ds.herbie.pick_points(points, method="nearest")
41
-
42
- # Extract the correct variable name dynamically
43
- if var_name == "10u":
44
- var_name_actual = "u10" # Map 10u to u10
45
- elif var_name == "10v":
46
- var_name_actual = "v10" # Map 10v to v10
47
- else:
48
- var_name_actual = var_name # For ssrd and tp, use the same name
49
-
50
- # Extract time series
51
- time_series = dsi[var_name_actual].squeeze()
52
-
53
- # Convert to DataFrame
54
- df = time_series.to_dataframe().reset_index()
55
-
56
- # Convert `valid_time` to datetime
57
- if "valid_time" in df.columns:
58
- df = df.rename(columns={"valid_time": "datetime"})
59
- elif "step" in df.columns and "time" in dsi.coords:
60
- df["datetime"] = dsi.time.values[0] + df["step"]
61
-
62
- # Keep only datetime and variable of interest
63
- df = df[["datetime", var_name_actual]].drop_duplicates()
64
-
65
- # Append to list
66
- dfs.append(df)
67
-
68
- # Print extracted data
69
- # print(df)
70
-
71
- # Merge all variables into a single DataFrame
72
- final_df = dfs[0]
73
- for df in dfs[1:]:
74
- final_df = final_df.merge(df, on="datetime", how="outer")
75
- print(final_df)
76
- # Calculate wind speed
77
- final_df["wind_speed"] = (final_df["u10"] ** 2 + final_df["v10"] ** 2) ** 0.5
78
-
79
- #rainfall corrected: OLS Regression Equation: Corrected Forecast = 0.7247 * Forecast + 0.1853
80
- final_df["tp_corrected"] = 0.7247 * final_df["tp"] + 0.1853
81
-
82
- #wind speed correction: Corrected Forecast = 0.4167 * Forecast + 4.1868
83
- final_df["wind_speed_corrected"] = 0.4167 * final_df["wind_speed"] + 4.1868
84
-
85
- #radiation correction will need to be fixed because it was done on fdir instead of ssdr
86
- #radiation corrected: Corrected Forecast = 0.0553 * Forecast - 0.0081
87
- final_df["ssrd_corrected"] = 0.0553 * final_df["ssrd"] - 0.0081
88
-
89
- # Setup the Open-Meteo API client with cache and retry on error
90
- cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
91
- retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
92
- openmeteo = openmeteo_requests.Client(session = retry_session)
93
-
94
- # Make sure all required weather variables are listed here
95
- # The order of variables in hourly or daily is important to assign them correctly below
96
- url = "https://api.open-meteo.com/v1/forecast"
97
- params = {
98
- "latitude": 26.9690,
99
- "longitude": -80.7976,
100
- "hourly": "evapotranspiration",
101
- "forecast_days": 16,
102
- "models": "gfs_seamless"
103
- }
104
- responses = openmeteo.weather_api(url, params=params)
105
-
106
-
107
- # Process first location. Add a for-loop for multiple locations or weather models
108
- response = responses[0]
109
-
110
- hourly = response.Hourly()
111
- hourly_evapotranspiration = hourly.Variables(0).ValuesAsNumpy()
112
-
113
- hourly_data = {"date": pd.date_range(
114
- start = pd.to_datetime(hourly.Time(), unit = "s", utc = True),
115
- end = pd.to_datetime(hourly.TimeEnd(), unit = "s", utc = True),
116
- freq = pd.Timedelta(seconds = hourly.Interval()),
117
- inclusive = "left"
118
- )}
119
-
120
- hourly_data["evapotranspiration"] = hourly_evapotranspiration
121
-
122
- hourly_dataframe = pd.DataFrame(data = hourly_data)
123
-
124
- # Convert datetime to date for merging
125
- final_df['date'] = final_df['datetime']
126
- # Ensure final_df['date'] is timezone-aware (convert to UTC)
127
- final_df['date'] = pd.to_datetime(final_df['date'], utc=True)
128
-
129
- # Ensure hourly_dataframe['date'] is also timezone-aware (convert to UTC)
130
- hourly_dataframe['date'] = pd.to_datetime(hourly_dataframe['date'], utc=True)
131
-
132
- # Merge while keeping only matching dates from final_df
133
- merged_df = final_df.merge(hourly_dataframe, on='date', how='left')
134
-
135
- # Print final combined DataFrame
136
- merged_df.drop(columns=['date'], inplace=True)
137
- # print(merged_df)
138
-
139
- merged_df.to_csv(file_path, index=False)
140
-
141
-
142
- def main():
143
- # Set up command-line argument parsing
144
- parser = argparse.ArgumentParser(description="Download and process weather forecast data.")
145
- parser.add_argument("file_path", help="Path to save the resulting CSV file.")
146
-
147
- # Parse the arguments
148
- args = parser.parse_args()
149
-
150
- # Call the function with the provided file path
151
- download_weather_forecast(args.file_path)
152
-
153
-
154
- if __name__ == "__main__":
155
- main()
File without changes